diff --git a/Hack Days Oct-2026.md b/Hack Days Oct-2026.md new file mode 100644 index 00000000..f9ef020d --- /dev/null +++ b/Hack Days Oct-2026.md @@ -0,0 +1,114 @@ +# Make the Internet Great Again… Again? + +Hey everyone, this is me, Alex. + +There‘s so many new faces, I'm pretty sure some of you have no idea who I am. + +Which is fine. Sometimes I'm not entirely sure myself. + +I'm Alex from the Apple team, working on our macOS browser. +And I thought I'd make it greater again. + +What would you expect from a macOS browser hack days project? + +They say Jesus was a hacker. Performing live demos: turning water into wine. +Accepting some cookies. + +Not bad for a Hack Days project, eh? + +Alright, the show time! + + + + + + + + +**[Start the screen sharing with https://infiniteslop.ai/ open]** + +Hey, I hope you can hear the sound. +[turn off sound] + +But seriously. What's happening to the world? + +AI everywhere. Too much information. Everything looks the same. + +[https://liquid-glass-eta.vercel.app/webgl.html] + +Everything is a Liquid Ass. And BEIGE! + +Sometimes I just want to burn it all down. + +**[Click Fire — show the new fire animation]** + +MUCH better! + +Let's do it again! + +**[Click Fire — show the new fire animation]** + +Where shall we Start now? + +**[Click Start]** + +**Sometimes you just want to run away, back to something familiar.** + +**[Click Run → enter Bookmark search mode]** + +**Or Search for happiness** + +Or maybe you just need someone to talk someone to. + +**[Run → AI — open Duck.ai -> ask "Is it normal to miss the internet from 1998?"**]** + +**But maybe the problem isn't the internet.** + +Maybe it's how we see it. + +I wish I could change the settings of my life. + +**[Open Settings]** + +Change how the world Appears. + +**[Appearance]** + +What if we could go back? + +**[Select Retro theme]** + +Somewhere like THIS. When buttons looked like buttons. Windows looked like windows. + +**[Open New Tab Page]** + +Smells like Christmas mornings, hot chocolate, and mandarins. Back when everybody we miss were still around… + +**[Open https://www.spacejam.com/1996/]** + +Remember when websites had personality? + +**When every page told you someone had paid attention to it…** + +And speaking of the little things… + +**[Shrink browser window; Hover over a link near the bottom-left corner]** + +Even the link tooltip now knows when to move out of your way. + +It respects your space. + +Your privacy. + +Just Start… + +**[Click Start]** + +And ask for Help. + +**[Click Help]** + +And here you go. + +Your protection. Your privacy. Your peace of mind. + diff --git a/family/documents/movies-watchlist.md b/family/documents/movies-watchlist.md index 02da0a65..f084aa2d 100755 --- a/family/documents/movies-watchlist.md +++ b/family/documents/movies-watchlist.md @@ -18,6 +18,19 @@ related: - "[[family/documents/karaoke-songs]]" --- ## Новинки - 👍/👎 + - [ ] 🗓️ [Большие гонки. Пегас III](https://www.kinopoisk.ru/film/11026619/) (2026) IMDb 6.5 + - [ ] 🗓️ [5 сантиметров в секунду](https://www.kinopoisk.ru/film/8230870/) (2025) IMDb 7.3 + - [ ] 🗓️ [Плетёный муж](https://www.kinopoisk.ru/film/11095481/) (2026) IMDb 8.0 KP 7.4 + - [ ] 🗓️ [Заговор «Кондор»](https://www.kinopoisk.ru/film/14827448/) (2025) IMDb 6.7 + - [ ] 🗓️ [Маленькие создания](https://www.kinopoisk.ru/film/14827447/) (2025) IMDb 6.9 + - [ ] 🗓️ [Монитор смерти](https://www.kinopoisk.ru/film/14260677/) (2026) IMDb 6.2 + - [ ] 🗓️ [Ограбление по-бразильски](https://www.kinopoisk.ru/film/14827446/) (2025) IMDb 7.3 + - [ ] 🗓️ [Увидеть всё](https://www.kinopoisk.ru/film/14001130/) (2026) IMDb 9.3 + - [ ] 🗓️ [Отверженные](https://www.kinopoisk.ru/film/7856055/) (2026) IMDb 7.0 + - [ ] 🗓️ [Мама. Зло внутри](https://www.kinopoisk.ru/film/8550743/) (2025) IMDb 6.2 KP 5.4 + - [ ] 🗓️ [Любовь и ярость](https://www.kinopoisk.ru/film/8723442/) (2025) IMDb 6.2 + - [ ] 🗓️ [Арт-лекторий: Караваджо: Се человек](https://www.kinopoisk.ru/film/13041648/) (2025) IMDb 6.7 + - [ ] 🗓️ [Кейт Мосс и Фрейд](https://www.kinopoisk.ru/film/10644854/) (2025) IMDb 6.7 - [ ] 🗓️ [Дузе](https://www.kinopoisk.ru/film/6556228/) (2025) IMDb 6.0 - [ ] 🗓️ [Как ограбить банк](https://www.kinopoisk.ru/film/6382694/) (2026) IMDb 7.0 - [ ] 🗓️ [Фьорд](https://www.kinopoisk.ru/film/7029820/) (2026) IMDb 7.6 KP 7.2 @@ -403,7 +416,7 @@ related: - [ ] ⬇️ Лучшее предложение - [ ] ❓ Итальянцы в ..? - Italiani a Rio (1987) ★4.1 - - Невероятные приключения итальянцев в России (1974) ★7.2 + - Невероятные приключения итальянцев в России (1974) ★7.3 - [ ] Римские каникулы - [ ] Покровские ворота - [ ] Индиана Джонс и последний @@ -508,7 +521,7 @@ related: - [ ] ❓ Загадочное убийство - Загадочное убийство (2020) ★5.3 - Загадочное убийство Белль (2025) ★5.8 - - Загадочное убийство в Манхэттэне (1993) ★7.3 + - Загадочное убийство в Манхэттэне (1993) ★7.2 - [ ] Игра в имитацию - [ ] ❓ Ирана - [ ] Неуловимые мстители diff --git a/family/documents/movies-watchlist.md.bak-questions b/family/documents/movies-watchlist.md.bak-questions index 66b561ea..1006d6f3 100755 --- a/family/documents/movies-watchlist.md.bak-questions +++ b/family/documents/movies-watchlist.md.bak-questions @@ -18,6 +18,12 @@ related: - "[[family/documents/karaoke-songs]]" --- ## Новинки - 👍/👎 + - [ ] 🗓️ [Увидеть всё](https://www.kinopoisk.ru/film/14001130/) (2026) IMDb 9.3 + - [ ] 🗓️ [Отверженные](https://www.kinopoisk.ru/film/7856055/) (2026) IMDb 7.0 + - [ ] 🗓️ [Мама. Зло внутри](https://www.kinopoisk.ru/film/8550743/) (2025) IMDb 6.2 KP 5.4 + - [ ] 🗓️ [Любовь и ярость](https://www.kinopoisk.ru/film/8723442/) (2025) IMDb 6.2 + - [ ] 🗓️ [Арт-лекторий: Караваджо: Се человек](https://www.kinopoisk.ru/film/13041648/) (2025) IMDb 6.7 + - [ ] 🗓️ [Кейт Мосс и Фрейд](https://www.kinopoisk.ru/film/10644854/) (2025) IMDb 6.7 - [ ] 🗓️ [Дузе](https://www.kinopoisk.ru/film/6556228/) (2025) IMDb 6.0 - [ ] 🗓️ [Как ограбить банк](https://www.kinopoisk.ru/film/6382694/) (2026) IMDb 7.0 - [ ] 🗓️ [Фьорд](https://www.kinopoisk.ru/film/7029820/) (2026) IMDb 7.6 KP 7.2 @@ -402,7 +408,7 @@ related: - [ ] ⬇️ Лучшее предложение - [ ] ❓ Итальянцы в ..? - Italiani a Rio (1987) ★4.1 - - Невероятные приключения итальянцев в России (1974) ★7.2 + - Невероятные приключения итальянцев в России (1974) ★7.3 - [ ] Римские каникулы - [ ] Покровские ворота - [ ] Индиана Джонс и последний @@ -507,7 +513,7 @@ related: - [ ] ❓ Загадочное убийство - Загадочное убийство (2020) ★5.3 - Загадочное убийство Белль (2025) ★5.8 - - Загадочное убийство в Манхэттэне (1993) ★7.3 + - Загадочное убийство в Манхэттэне (1993) ★7.2 - [ ] Игра в имитацию - [ ] ❓ Ирана - [ ] Неуловимые мстители diff --git a/family/how-to/arr-stack-taiga.md b/family/how-to/arr-stack-taiga.md index a33eb3ba..06592409 100755 --- a/family/how-to/arr-stack-taiga.md +++ b/family/how-to/arr-stack-taiga.md @@ -104,6 +104,8 @@ proxy + flaresolverr = [1,2] Подробности: [[personal/tech/flaresolverr-taiga]]. +Повторный сбой 2026-10-26 UTC: реальные отказы RuTracker сопровождались локальной ошибкой Prowlarr `database is locked`; подобные блокировки затрагивали и другие индексаторы, затем Lidarr. Отдельно исправлен watchdog FlareSolverr: признак входа теперь проверяется в HTML после разбора JSON, временная ошибка запроса не сбрасывает рабочую сессию. Проверено: 50 результатов реального поиска, успешный одиночный тест RuTracker в Lidarr, после `CheckHealth` предупреждение о недоступности индексаторов исчезло. Высокое ожидание дискового I/O и блокировки SQLite требуют отдельного разбора; исправление watchdog их не устраняет. + ## Перезапуск и приёмка ```bash diff --git a/family/how-to/home-automation.md b/family/how-to/home-automation.md index 7b4ad104..10ccb663 100755 --- a/family/how-to/home-automation.md +++ b/family/how-to/home-automation.md @@ -869,10 +869,12 @@ Hz:PWM 0:0 1:0 2:0 3:0 4:48 5:55 6:63 7:70 8:78 9:85 10:92 11:97 12:102 13:107 1 ``` **Ручное управление AT2 с панели:** PRG → `P10`=0 → FUNC/DATA → `P11`=0 → FUNC/DATA → PRG. +**Управление частотой крутилкой на приборе: `P10=1`.** Возврат на внешнее: **P10=2, P11=2, P50=5**. **Параметры AT2:** +<<<<<<< HEAD | Параметр | Значение | Смысл | | --------- | ------------ | ------------------------------------------------------ | | P06 | 50 | макс. рабочая частота | @@ -886,6 +888,26 @@ Hz:PWM 0:0 1:0 2:0 3:0 4:48 5:55 6:63 7:70 8:78 9:85 10:92 11:97 12:102 13:107 1 | P73 / P74 | 15720 / 2096 | калибровка 0–5 В (макс / мин) | | P77 | 54321 | полный сброс | | | | | +======= +| Параметр | Значение | Смысл | +|---|---|---| +| P00 | 220 В | максимальное выходное напряжение | +| P01 | 50 Гц | опорная (базовая) частота характеристики U/f, соответствующая максимальному выходному напряжению P00 | +| P02 | 110 В | напряжение на промежуточной частоте P03 | +| P03 | 25 Гц | промежуточная частота характеристики U/f | +| P06 | 50 | макс. рабочая частота | +| P10 | 2 | источник частоты — внешний аналог | +| P11 | 2 | RUN/STOP — внешние входы | +| P12 | 1 | плавное торможение (0 = мотор раскручивается при STOP) | +| P34 / P42 | 20–50 | ускорение / торможение, Гц/с | +| P50 | 5 | X1 = RUN | +| P58 | 3 | SP1 = fault indication | +| P62 | 1 | дисплей = выходная частота (4 = темп. радиатора) | +| P73 / P74 | 15720 / 2096 | калибровка 0–5 В (макс / мин) | +| P77 | 54321 | полный сброс | +| P92 | 2 | режим трёхпроводного однофазного двигателя | +| P98 | 0 Гц | управление выходом U активно во всём диапазоне частот | +>>>>>>> origin/main Входы X1–X6 активны **замыканием на COM** (оптопара PC817). `5V/10V OUT` — питание потенциометров, управляющий сигнал идёт на **VI1**. diff --git a/family/how-to/htpc-webos-luna.md b/family/how-to/htpc-webos-luna.md index 651afb3d..1f9f3cb0 100755 --- a/family/how-to/htpc-webos-luna.md +++ b/family/how-to/htpc-webos-luna.md @@ -135,6 +135,10 @@ ssh -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -i ~/.ssh/id_rsa \ root@192.168.1.70 'cat /sys/class/net/eth0/speed; cat /sys/class/net/eth0/duplex' ``` +## Другой телевизор: Новосибирск + +Эта заметка описывает ТВ `192.168.1.70` / webOS 5.4.2. **Konka Q85 85″ в Новосибирске — отдельное устройство:** `192.168.2.124`, webOS 7.5.6. Его root, отключённые службы, результаты проверки и порядок отката зафиксированы в [[personal/tech/konka-q85-novosibirsk]] (2026-10-30). Не переносить между ними SSH-параметры и особенности `luna-send` без проверки. + ## Связанные заметки - [[family/how-to/tv-luna-send]] — враппер `~/Automation/tv-luna-send.sh` diff --git a/family/how-to/kraken-access.md b/family/how-to/kraken-access.md index d77ad595..d90f465b 100755 --- a/family/how-to/kraken-access.md +++ b/family/how-to/kraken-access.md @@ -1,27 +1,20 @@ # Kraken — Внешний доступ -> # ✅ 2026-09-23 — КАНАЛ REVERSE ЖИВ + ТОПОЛОГИЯ УТОЧНЕНА -> -> **1. Reverse-туннель работает** (первая проверка после фикса гонки 2026-09-20): SOCKS `xray-reverse-portal:12345` → `92.62.77.233` ≠ TrueNAS `90.189.160.148`. Детали — [[personal/tech/xray-reverse-tunnel-kraken-truenas]]. -> -> 🔴 **2. IP Кра́кена ИЗМЕНИЛСЯ: `92.62.70.41` → `92.62.77.233`.** Прежний зафиксирован в этом документе и в `truenas-infrastructure.md` — при проверках брать актуальный из живого запроса. -> -> **3. Топология Кра́кена (проверено `ip -4 addr` + `ip route`, 2026-09-23):** -> -> ``` -> eth0 192.168.1.14 metric 100 ← активный default route -> wlan0 192.168.1.15 metric 600 -> default via 192.168.1.1 dev eth0 src 192.168.1.14 -> wg0 10.99.1.2/24 (VPS qentra удалён — звено мёртво) -> ``` -> -> 🔴 **Кра́кен и Mac (`192.168.1.57`) — СОСЕДИ в одной подсети `192.168.1.0/24`**, шлюз `192.168.1.1` (ASUS RT-AC68U). **`192.168.2.2` (OpenWrt) — это шлюз сети TrueNAS, к сети Mac/Кра́кена отношения не имеет.** Не приписывать OpenWrt сети `192.168.1.x`. -> -> ⚠️ Следствие: с Mac до `192.168.2.197` маршрут идёт `gateway: 192.168.1.1` (проверено `route -n get`), а не через OpenWrt. Схема «статический маршрут `192.168.2.0/24 → 192.168.1.15` на ASUS» **работоспособна**, потому что Кра́кен — сосед в той же подсети, и обратный NAT делает он сам. Нюанс интерфейса (`.14` eth0 vs `.15` wlan0) — в [[personal/plans/lan-via-reverse-tunnel]]. -> -> ⚠️ **ASUS `192.168.1.1` по SSH недоступен** (`Permission denied` → `Connection closed`, порт 22 `closed`). Раньше в доке `home-network-config` вход был описан как `ssh -i ~/.ssh/id_rsa admin@192.168.1.1` — **сейчас не работает**. Правки маршрутов — только через веб-UI. -> -> **4. `xray-reverse-bridge` не имеет `inbounds`** — ключи конфига только `log`/`outbounds`/`routing`. Слушающих портов нет. Канал работает на отправку, принять трафик от роутера бриджу нечем. Разбор — [[personal/plans/lan-via-reverse-tunnel]]. +## Действующий доступ и reverse-туннель + +Проверено 2026-10-30: + +```bash +ssh kraken@ssh-kraken.qentra.top +``` + +Внешний SSH работает через Cloudflare. LAN-адрес Kraken: `192.168.1.14` (eth0). + +Reverse-туннель TrueNAS → Kraken работает. Проверенный WAN-IP Kraken: `217.29.24.185`; адрес динамический, получать актуальный живым запросом. + +Monit только сообщает об ошибках Docker и не перезапускает Docker/containerd. Запуском Docker управляет systemd; запуск bridge обеспечивает включённая служба `xray-reverse-bridge.service` после готовности Docker. Зависимость Docker от монтирования диска сохранена. Перезагрузкой хоста новая настройка пока не проверена. + +Конфигурация и команды проверки: [[personal/tech/xray-reverse-tunnel-kraken-truenas#Эксплуатация и автозапуск]]. > # ✅ 2026-09-20 — НАЙДЕНА ИСТИННАЯ ПРИЧИНА (2026-09-15 поломки): ГОНКА Docker ↔ монтирование USB-диска > diff --git a/family/how-to/rasputin-router.md b/family/how-to/rasputin-router.md index 0e2c3ef7..554ce7ce 100755 --- a/family/how-to/rasputin-router.md +++ b/family/how-to/rasputin-router.md @@ -1,7 +1,7 @@ --- title: Rasputin OpenWrt router created: '2026-07-22' -updated: '2026-09-17' +updated: '2026-10-30' tags: - openwrt - router @@ -12,8 +12,9 @@ tags: --- # Rasputin R5S OpenWrt router -> Status as of 2026-09-22: Rasputin is the OpenWrt edge/LAN router, routing normal LAN traffic through a VLESS/Xray tunnel via `tun0`, with explicit direct-WAN exceptions for upstream LAN access and Mac `cloudflared` QUIC traffic. 🔴 **WAN СНОВА ПЕРЕЕХАЛ (2026-09-22): `eth0` теперь `192.168.1.50/24`, gw `192.168.1.1`, MAC `36:ae:87:04:08:dc`, линк `1000/full`** — Распутин воткнут в сегмент `192.168.1.0/24`, где живут Кра́кен (`.14`/`.15`) и ТВ (`.70`). Прежний `192.168.2.157/24` (gw `192.168.2.2`) больше не активен. -> 🔴 **Мак теперь в LAN Распутина** — `192.168.6.173` (en0), а НЕ `192.168.1.57`. Транзит из LAN `192.168.6.0/24` в `192.168.1.0/24` **СЛОМАН** (тот же класс проблемы, что и GPON-сегмент): правила `100` покрывают только `192.168.2.0/24` и `192.168.0.0/24`. См. раздел «🔴 Транзит из LAN в 192.168.1.0/24». +Rasputin — LAN-роутер OpenWrt: `192.168.6.1/24`, WAN `eth0` — `192.168.2.157/24`, шлюз `192.168.2.2`. Клиентский TCP-трафик идёт через `tun0` → `badvpn-tun2socks` → SOCKS Xray → TrueNAS → reverse-туннель → Kraken → интернет. Для локальных сетей аплинка и Mac `cloudflared` QUIC действуют прямые исключения. + +DNS клиентов обслуживает отдельная служба `clientdns` через тот же туннель. Системный dnsmasq и DNS самого роутера работают отдельно. SSH: `ssh root@192.168.6.1`. ## Identity @@ -40,21 +41,13 @@ LAN clients -> selected exceptions: table main -> WAN device ``` -WAN devices are `eth0` and `usb0` in the `wan` firewall zone. **As of 2026-09-17 `eth0` is the active WAN** (it carries the default route); `usb0` is idle. At the 2026-07-22 capture this was inverted — `eth0` had no carrier and `usb0` held the default route. - -Default route at the 2026-07-22 capture (historical, no longer active): - -```text -default via 10.252.240.133 dev usb0 src 10.252.240.230 -``` - -Current default route (2026-09-17): +В зоне `wan` находятся `eth0` и `usb0`; активный аплинк — `eth0`. ```text default via 192.168.2.2 dev eth0 proto static src 192.168.2.157 ``` -That is, Rasputin gets `192.168.2.157/24` from the upstream router `192.168.2.2` and reaches the internet through it; `192.168.2.2` in turn sits behind GPON `192.168.0.1`. +Аплинк: Rasputin → `192.168.2.2` → GPON `192.168.0.1`. Туннель подключается к TrueNAS `192.168.2.197:8443` через локальную сеть аплинка. VPN table: @@ -137,7 +130,7 @@ dhcp.lan.interface='lan' dhcp.lan.start='100' dhcp.lan.limit='150' dhcp.lan.leasetime='12h' -dhcp.lan.dhcp_option='6,1.1.1.1,1.0.0.1' +dhcp.lan.dhcp_option='6,192.168.6.1' dhcp.lan.dhcpv4='server' dhcp.lan.dhcpv6='server' dhcp.lan.ra='server' @@ -163,6 +156,233 @@ Backup made before this change: /etc/config/dhcp.bak.20260714-195408 ``` +## Клиентский DNS через туннель + +DHCP выдаёт клиентам `192.168.6.1`: + +```text +dhcp.lan.dhcp_option='6,192.168.6.1' +``` + +```text +LAN-клиент → адрес Rasputin:53 (UDP/TCP) + → firewall dstnat_lan → redirect :1053 + → отдельный dnsmasq службы clientdns + → локальные имена: системный dnsmasq 127.0.0.1:53 + → внешний DNS: отдельный Xray 127.0.0.1:1054 + → SOCKS основного Xray 127.0.0.1:1080 + → VLESS → TrueNAS → reverse-туннель → Kraken + → Cloudflare: A/AAAA через DoH; другие типы через DNS-over-TCP +``` + +`clientdns` не меняет upstream системного dnsmasq и встроенный DNS основного Xray. Системный dnsmasq слушает порт 53 и использует `8.8.8.8` из `/tmp/resolv.conf.d/resolv.conf.auto`; запросы самого роутера не перенаправляются. DNS аплинка `192.168.2.2` настроен на `1.1.1.1` и полученный по WAN `192.168.0.1`. + +Обрабатываются только входящие из LAN TCP/UDP-запросы к DNS-адресам Rasputin: `192.168.6.1`, `fd3b:153e:9be9::1` и `fe80::34ae:87ff:fe04:8dd`. Запросы к произвольным внешним DNS-адресам это правило не перехватывает. При смене LAN IPv6-адресов нужно обновить `/etc/clientdns/redirect.nft`. + +Общая передача клиентского UDP через текущий tun2socks не настроена. Для DNS это не требуется: клиентский UDP заканчивается на Rasputin, а дальше запрос идёт по TCP через прокси. При недоступности основного Xray/reverse-туннеля внешний клиентский DNS также недоступен; прямого fallback через WAN нет. + +### Файлы и параметры clientdns + +`/etc/init.d/clientdns` — отдельная служба procd с двумя экземплярами: `doh` (Xray) и `resolver` (dnsmasq). Автозапуск включён, `START=99`, `STOP=10`; для обоих процессов настроен `respawn 3600 5 5`. Ссылки автозапуска: `/etc/rc.d/S99clientdns` и `/etc/rc.d/K10clientdns`. После загрузки внешний клиентский DNS становится доступен, когда основной Xray и reverse-туннель готовы принимать запросы. Параметры и правила firewall сохранены в `/etc`; проверка enable/disable выполняется без остановки процессов. + +`/etc/clientdns/dnsmasq.conf`: + +```ini +# Dedicated client resolver; the system dnsmasq remains on port 53. +port=1053 +interface=br-lan +interface=lo +bind-dynamic +no-resolv +no-poll +no-hosts +cache-size=1000 +edns-packet-max=1232 +server=127.0.0.1#1054 +# Preserve the existing router's local name service. +server=/lan/local/127.0.0.1#53 +server=//127.0.0.1#53 +server=/6.168.192.in-addr.arpa/ip6.arpa/127.0.0.1#53 +user=dnsmasq +group=dnsmasq +log-facility=/tmp/clientdns-dnsmasq.log +``` + +Локальные домены `lan`, `local`, имена без доменной части, обратные запросы для `192.168.6.0/24` и `ip6.arpa` передаются прежнему dnsmasq. Клиентский экземпляр не раздаёт DHCP. + +`/etc/clientdns/xray.json`: + +```json +{ + "log": { + "loglevel": "warning", + "access": "/tmp/clientdns-xray-access.log", + "error": "/tmp/clientdns-xray-error.log" + }, + "inbounds": [ + { + "tag": "clientdns-in", + "listen": "127.0.0.1", + "port": 1054, + "protocol": "dokodemo-door", + "settings": { + "address": "1.1.1.1", + "port": 53, + "network": "tcp,udp" + } + } + ], + "outbounds": [ + { + "tag": "tunnel", + "protocol": "socks", + "settings": { + "servers": [ + { + "address": "127.0.0.1", + "port": 1080 + } + ] + } + }, + { + "tag": "dns-out", + "protocol": "dns", + "settings": { + "network": "tcp", + "address": "1.1.1.1", + "port": 53, + "nonIPQuery": "skip" + }, + "proxySettings": { + "tag": "tunnel" + } + } + ], + "routing": { + "domainStrategy": "AsIs", + "rules": [ + { + "type": "field", + "inboundTag": [ + "clientdns-in" + ], + "outboundTag": "dns-out" + }, + { + "type": "field", + "inboundTag": [ + "clientdns-upstream" + ], + "outboundTag": "tunnel" + } + ] + }, + "dns": { + "servers": [ + "https://1.1.1.1/dns-query" + ], + "queryStrategy": "UseIP", + "tag": "clientdns-upstream" + } +} +``` + +У отдельного Xray нет собственного VLESS-подключения: он использует существующий SOCKS `127.0.0.1:1080`. A/AAAA разрешаются через `https://1.1.1.1/dns-query`; остальные типы (`MX`, `TXT`, `HTTPS` и другие) передаются к `1.1.1.1:53` по TCP через тот же SOCKS. `UseIP` относится только к отдельному DNS-процессу; основной Xray сохраняет `UseIPv4`. + +Подключение правил в `/etc/config/firewall`: + +```uci +config include 'clientdns_redirect' + option type 'nftables' + option path '/etc/clientdns/redirect.nft' + option chain 'dstnat_lan' + option position 'chain-prepend' +``` + +`/etc/clientdns/redirect.nft`: + +```nft +ip daddr 192.168.6.1 meta l4proto { tcp, udp } th dport 53 counter redirect to :1053 comment "clientdns-v4" +ip6 daddr { fd3b:153e:9be9::1, fe80::34ae:87ff:fe04:8dd } meta l4proto { tcp, udp } th dport 53 counter redirect to :1053 comment "clientdns-v6" +``` + +### Управление и диагностика clientdns + +На роутере: + +```sh +/etc/init.d/clientdns status +/etc/init.d/clientdns enabled +ubus call service list '{"name":"clientdns"}' +/usr/bin/xray run -test -config /etc/clientdns/xray.json < /dev/null +/usr/sbin/dnsmasq --test -C /etc/clientdns/dnsmasq.conf +netstat -lnp | grep -E ':(1053|1054) ' +uci show firewall.clientdns_redirect +nft list chain inet fw4 dstnat_lan +``` + +Оба экземпляра procd должны быть `running`; при клиентских запросах растут счётчики `clientdns-v4`/`clientdns-v6`. + +Проверка обработчика на роутере и отдельной маршрутизации локальных имён: + +```sh +nslookup www.youtube.com 127.0.0.1:1053 +nslookup rasputin.lan 127.0.0.1:1053 +``` + +Проверка с LAN-клиента через стандартный DNS-порт: + +```sh +dig @192.168.6.1 www.youtube.com A +dig +tcp @192.168.6.1 www.youtube.com A +dig @192.168.6.1 rasputin.lan A +dig @192.168.6.1 gmail.com MX +dig @192.168.6.1 cloudflare.com TYPE65 +curl -4 -fsS -o /dev/null --max-time 20 -w 'HTTP %{http_code}\n' https://www.youtube.com +``` + +`TYPE65` — запись HTTPS; такой синтаксис подходит для штатного macOS dig. Ожидаются ответы DNS и `HTTP 200`. Запрос на роутере к `127.0.0.1:53` проверяет системный DNS, а не клиентский путь. + +Логи отдельной службы: + +```sh +tail -100 /tmp/clientdns-dnsmasq.log +tail -100 /tmp/clientdns-xray-error.log +tail -100 /tmp/clientdns-xray-access.log +grep '1.1.1.1:443' /tmp/xray-access.log | tail -10 +``` + +Перезапуск только клиентского DNS: + +```sh +/etc/init.d/clientdns restart +``` + +### Отключение и откат clientdns + +Подключиться по IP: `ssh root@192.168.6.1`. На роутере выполнить: + +```sh +sh /etc/clientdns/rollback.sh +``` + +Эквивалентные ручные команды: + +```sh +uci -q delete firewall.clientdns_redirect +uci commit firewall +/etc/init.d/firewall reload +/etc/init.d/clientdns stop +/etc/init.d/clientdns disable +``` + +После этого клиентские запросы снова обрабатывает исходный dnsmasq на порту 53. Менять DHCP или настройки клиентов не нужно. Возвращается обычный DNS через WAN, вместе с его ограничениями. + +Копия скрипта отката и исходные конфиги находятся на роутере в `/root/clientdns-backup-20261030-134826/`: `rollback.sh`, `firewall`, `firewall.uci`, `dhcp`, `xray-config.json`. Для отключения clientdns достаточно удалить его отдельный блок firewall; восстановление всего firewall из копии может затереть другие последующие изменения. + +При полном удалении сначала выполнить откат, затем удалить `/etc/init.d/clientdns` и `/etc/clientdns/`. Нельзя удалять `redirect.nft`, пока на него ссылается включённый блок firewall. + ## VLESS / Xray Stack Packages present include: @@ -185,20 +405,23 @@ Runtime processes: ```text badvpn-tun2socks --tundev tun0 --netif-ipaddr 10.99.0.2 --netif-netmask 255.255.255.252 --socks-server-addr 127.0.0.1:1080 --loglevel none -/usr/bin/xray run -confdir /etc/xray -config /etc/xray/config.json -format json +/usr/bin/xray run -config /etc/xray/config.json -format json ``` `v2raya` is installed, but the observed UCI service config previously had `enabled='0'`. The active traffic path is the custom `tunvpn` service plus direct Xray config. ### Xray Config -File: +Основной Xray управляется OpenWrt procd через `/etc/init.d/xray`. Конфиг: `/etc/xray/config.json`; UCI: `/etc/config/xray`; ресурсы: `/usr/share/xray`. Автозапуск включён. ```text -/etc/xray/config.json +xray.enabled.enabled='1' +xray.config.conffiles='/etc/xray/config.json' +xray.config.datadir='/usr/share/xray' +xray.config.format='json' ``` -Structure: +SOCKS-вход: `127.0.0.1:1080`. Выход: VLESS поверх WebSocket/TLS к TrueNAS `192.168.2.197:8443`, SNI/Host `vpn.mallexxx.duckdns.org`, путь `/vless`. UUID в документации скрыт; брать его из действующего конфига. ```json { @@ -222,12 +445,11 @@ Structure: "settings": { "vnext": [ { - "address": "188.239.191.235", - "port": 443, + "address": "192.168.2.197", + "port": 8443, "users": [ { - "id": "", - "flow": "xtls-rprx-vision", + "id": "", "encryption": "none" } ] @@ -235,26 +457,104 @@ Structure: ] }, "streamSettings": { - "network": "tcp", - "security": "reality", - "realitySettings": { - "serverName": "node3.sysnx.net", - "publicKey": "", - "shortId": "", - "fingerprint": "qq" + "network": "ws", + "security": "tls", + "tlsSettings": { + "serverName": "vpn.mallexxx.duckdns.org" + }, + "wsSettings": { + "path": "/vless", + "headers": { + "Host": "vpn.mallexxx.duckdns.org" + } } } + }, + { + "tag": "direct", + "protocol": "freedom" + }, + { + "tag": "block", + "protocol": "blackhole" } ], "routing": { + "domainStrategy": "IPIfNonMatch", "rules": [ - { "type": "field", "outboundTag": "proxy", "network": "tcp,udp" } + { + "type": "field", + "protocol": [ + "bittorrent" + ], + "outboundTag": "block" + } ] + }, + "dns": { + "servers": [ + { + "address": "223.5.5.5", + "port": 53 + }, + { + "address": "8.8.8.8", + "port": 53 + } + ], + "queryStrategy": "UseIPv4" } } ``` -Secrets are intentionally redacted in this note. The live file on the router contains the actual VLESS UUID and REALITY parameters. +### Перезапуск и диагностика Xray + +Команды выполняются на Rasputin после `ssh root@192.168.6.1`. + +Проверить конфиг перед перезапуском; успешный результат — `Configuration OK.`: + +```sh +/usr/bin/xray run -test -config /etc/xray/config.json < /dev/null +``` + +Перезапустить только основную службу Xray: + +```sh +/etc/init.d/xray restart +/etc/init.d/xray status +``` + +Проверить автозапуск и состояние процесса под procd: + +```sh +/etc/init.d/xray enabled +ubus call service list '{"name":"xray"}' +uci show xray +netstat -lnp | grep '127.0.0.1:1080' +``` + +`status` должен выводить `running`; `enabled` завершается с кодом 0, если автозапуск включён. В ответе ubus у экземпляра `xray` должно быть `"running": true`; TCP-порт `127.0.0.1:1080` должен слушаться. + +Логи: + +```sh +tail -100 /tmp/xray-error.log +tail -100 /tmp/xray-access.log +logread -e xray +``` + +Файлы в `/tmp` очищаются при перезагрузке. В access-log `[socks-in >> proxy]` означает передачу запроса в основной VLESS-выход. + +Сквозная проверка основного прокси с удалённым разрешением имени: + +```sh +curl -4 -fsS --max-time 20 --socks5-hostname 127.0.0.1:1080 https://api.ipify.org +curl -4 -fsS -o /dev/null --max-time 20 --socks5-hostname 127.0.0.1:1080 -w 'HTTP %{http_code}\n' https://www.youtube.com +``` + +Первый запрос должен показать текущий внешний IP Kraken, второй — `HTTP 200`. IP Kraken динамический; для сравнения получить его непосредственно на Kraken. + +`xray`, `tunvpn` и `clientdns` — разные службы. Перезапуск `xray` не пересоздаёт `tun0` и policy routing; для их проверки использовать `ip rule show`, `ip route show table vpn` и `ip link show tun0`. Команда `/etc/init.d/tunvpn restart` перезапускает tun2socks и маршрутизацию. Клиентский DNS перезапускается через `/etc/init.d/clientdns restart`. Не использовать `killall xray`: он остановит также отдельный процесс Xray службы `clientdns`. ## `tunvpn` Service @@ -789,7 +1089,9 @@ Allow-Mac-cloudflared-QUIC-direct Mac 192.168.6.104 UDP/7844 -> main routing table -> WAN direct Mac 192.168.6.173 UDP/7844 -> main routing table -> WAN direct Any LAN client -> 192.168.2.0/24 -> main routing table -> WAN/upstream LAN -Other LAN traffic -> vpn routing table -> tun0 -> Xray/VLESS +LAN DNS to Rasputin:53 -> clientdns -> SOCKS/Xray -> TrueNAS -> Kraken +Other LAN TCP traffic -> vpn routing table -> tun0 -> Xray/VLESS +General client UDP via tun2socks -> not configured LAN -> WAN not explicitly allowed -> rejected by KillSwitch ``` diff --git a/family/how-to/truenas-infrastructure.md b/family/how-to/truenas-infrastructure.md index c37838ae..12c02fd1 100755 --- a/family/how-to/truenas-infrastructure.md +++ b/family/how-to/truenas-infrastructure.md @@ -1,5 +1,13 @@ # TrueNAS — инфраструктура +## Lampac и Konka — добавлено 2026-10-30 + +На TrueNAS развёрнут официальный Lampac1.87.3 (amd64), обычный Compose в `/mnt/RED_2TB/docker/lampac`, LAN `http://192.168.2.197:9118`; контейнеры `lampac` и `lampac-egress`. Клиент Konka Q85 в Новосибирске: `192.168.2.124`. Полная эксплуатационная заметка — [[personal/tech/lampac-truenas]]. + +Отдельный egress обслуживает TMDB и постеры через существующий маршрут Kraken; глобальный прокси видео не включён, общий `vless-proxy` не менялся. История хранится на TrueNAS; Kraken использует ограниченный HTTPS API в существующем Caddy-сайте `git.mallexxx.duckdns.org/lampac-history`. Синхронизации SQLite нет. Caddy применён через reload, без перезапуска общего сервиса. + +Каталог генерирует только Kraken и публикует готовые JSON через ограниченный SSH-ключ. NAS делает локальный ежедневный бэкап истории в04:27 по часовому поясу хоста, хранит30 дней. Для совместной работы uid950 бэкапа и uid1000 Lampac нужны mode0664/group1000 для `TimeCode.sql` и WAL/SHM; операция записи после бэкапа проверена. Остальные исторические сведения об инфраструктуре ниже этим изменением не переаудировались. + > ✅ **ДОМАШНЯЯ АВТОМАТИЗАЦИЯ ПЕРЕНЕСЕНА НА HP t610 (2026-09-14).** HA, zigbee2mqtt, mosquitto, mbusd, modbus-bridge **и flows Node-RED** переехали на **t610** (HA OS, `192.168.2.176`). Единый документ по t610 — [[family/how-to/home-automation]]. **✅✅ TrueNAS-стек автоматизации ПОГАШЕН 2026-09-14 (ночная сессия, §5-кватер-Л плана):** `homeassistant`, `mbusd`, `mosquitto`, `nodered`, `zigbee2mqtt`, `modbus-bridge`, `ser2net` → `docker stop` + `docker update --restart=no` (**НЕ удалены**, папки `docker/*` целы — откат = `docker start` + `--restart=unless-stopped`). Проверено: порты 502/1883/8123/1880 свободны, `mallexxx.*` = 200, MQTT на t610 живой. Причина переноса: TrueNAS на пределе RAM (17/21 ГБ). > > **2026-09-14 (вечер-2, обновлено вечер-13) — Caddy ПЕРЕКЛЮЧЁН, Node-RED ПЕРЕНЕСЁН:** diff --git a/family/how-to/tv-lampac-plugin.md b/family/how-to/tv-lampac-plugin.md index 499619e6..069d1900 100755 --- a/family/how-to/tv-lampac-plugin.md +++ b/family/how-to/tv-lampac-plugin.md @@ -5,6 +5,7 @@ related: - '[[family/how-to/tv-luna-send]]' - '[[family/how-to/htpc-webos-luna]]' - '[[personal/tech/lampac-deployment]]' + - '[[personal/tech/lampac-truenas]]' tags: - tv - webos @@ -14,10 +15,18 @@ tags: - how-to title: tv-lampac-plugin — подключение плагина Lampa на LG TV по SSH type: how-to -updated: '2026-09-22' +updated: '2026-10-30' --- # tv-lampac-plugin.sh — подключение плагина Lampa к Lampac на LG TV +## 2026-10-30: отдельная установка в Новосибирске + +Для Konka Q85 `192.168.2.124` / webOS7.5.6 используется TrueNAS `http://192.168.2.197:9118`, приложение `com.lampac.tv`. Установка выполнена через существующий Homebrew, без включения SSH на ТВ. Инструкция и проверенные настройки — [[personal/tech/lampac-truenas]], сведения об устройстве — [[personal/tech/konka-q85-novosibirsk]]. + +Для встроенных постеров нужны `proxy_tmdb=true`, `proxy_tmdb_auto=false` на клиенте и серверный прокси TMDB/постеров. Видео глобально через прокси не направляется. Проверять обе группы изображений: CUB/TMDB и наш Catalog; успешный постер одной группы не доказывает работу другой. + +Рецепты ниже относятся прежде всего к прежнему телевизору Kraken `192.168.1.70`; не переносить его SSH/PTY-особенности на Konka без проверки. + > Создан 2026-09-22. Проблема, которую решает: добавить плагин/источник в Lampa на ТВ > **без пульта**, автоматически по SSH. > Связано: [[family/how-to/tv-luna-send]], [[family/how-to/htpc-webos-luna]], [[personal/tech/lampac-deployment]]. diff --git a/personal/creative/humor/В жизни – так мало приятных вещей.md b/personal/creative/humor/В жизни – так мало приятных вещей.md new file mode 100644 index 00000000..20595a54 --- /dev/null +++ b/personal/creative/humor/В жизни – так мало приятных вещей.md @@ -0,0 +1,155 @@ +В жизни – так мало приятных вещей: + +Миска горячих наваристых щей,  + +Писать с балкона на разных людей,  + +Выебать пару красивых блядей,  + +Всё же – проснуться в похмелии жутком,  + +Шубы собольи дарить проституткам,  + +Пиздить в столовой кефир и котлеты,  + +В Крым добираться втроём без билетов,  + +Робко глядеть на красоток журнальных,  + +Секс вагинальный, а также анальный,  + +По голубям пиздануть из рогатки,  + +Маршировать с автоматом и в скатке,  + +К другу ввалиться в четыре утра,  + +Палкой картошку катить из костра,  + + + +Бегать по лесу с трёхцветным фонариком,  + +Лопать девчонкам по праздникам шарики,  + +В долг у дружка взять три тыщи надолго,  + +Вдруг без причины уехать на Волгу,  + +Пить самогонку, спешить на свидание,  + +Списывать перед английским задание,  + +Мордой в салате встречать Новый Год,  + +В баню ходить «для богатых господ»,  + +Долго купаться в пруду на баллоне,  + +Спать в таиландском массажном салоне,  + +Физику в мае опять прогулять,  + +В лёт по фламинго дуплетом стрелять,  + +Вспомнить разлуку с улыбкою странной,  + +Фильм посмотреть про любовь иностранный,  + +Влезть выше всех на кривую сосну,  + +Сдуру влюбиться в чужую жену,  + +Марки колоний в альбом собирать,  + +Ночью в учительской тайно насрать,  + +В Брисбене слопать яишню с беконом,  + +Штамп получить о разводе законном,  + +Снизу девчонкам смотреть на трусы,  + +Жарить с горошком кусок колбасы,  + +Песни в подъезде орать под гитару,  + +Слушать Deep Purple, Битлов и «Шизгару»,  + +Жить на Гоа, но болеть за «Зенит»,  + +Видеть, что хуй твой как дикий стоит,  + +Чтоб в день рожденья купили «конструктор»,  + +Чтоб парашют не запутал инструктор,  + +Сделать из трубки стреляющий пестик,  + +Вдуть среди свадьбы случайной невесте,  + +Прыгнуть на зонтике в речку с моста,  + +Встретить в Париже живого Хвоста,  + +Кружку налить в пивняке-автомате,  + +Вскрыть штык-ножом банку кильки в томате,  + +Выскочить пьяным в слезах на мороз,  + +Вновь появиться с охапкою роз,  + +Порно смотреть и ебаться жестоко,  + +Выпить с ребятами из «Кровостока»,  + +Спать с незнакомкой под вздохи Том-Уэйтса,  + +Опохмелиться на всё, что имеется,  + +Вылечить триппер и шляться по Ницце,  + +В сером халате съебать из больницы,  + +Ночью шампанское пить в Коктебеле,  + +Петь под пластинку Вано Мурадели,  + +Смыться с компанией из ресторана,  + +Сдёрнуть за сутки четыре стоп-крана,  + +Спрыгнуть в цветы на степном полустанке,  + +Джинсы купить у седой итальянки,  + +Ехать куда-то пять дней автостопом,  + +Выть под Майлс Дэвиса злые синкопы,  + +«Хуй» на машине гвоздём написать,  + +Двух каратистов на танцах не ссать,  + +Водку закусывать спинкой от тюльки,  + +В дождь, в три утра дозвониться до Юльки,  + +Дёрнуть из речки крючок с карасём,  + + + +Вот, к сожалению, в общем, и всё,  + +Что привлекает по жизни меня,  + +Всё остальное – поверьте – хуйня,  + +Жизнь состоит из прыщей и клещей,  + +В жизни так мало хороших вещей!  + + + +(с) orlusha \ No newline at end of file diff --git a/personal/creative/humor/В каморку папы Карло постучали.md b/personal/creative/humor/В каморку папы Карло постучали.md new file mode 100644 index 00000000..1fd3f69f --- /dev/null +++ b/personal/creative/humor/В каморку папы Карло постучали.md @@ -0,0 +1,241 @@ +- Батя, ты? - голос у Буратино скрипел, как потертое седло.  + +- Открывай, свои, - нетрезво пробухтел кто-то.  + +- Свои в это время дома сидят. Пароль говори!  + +- Не все коту матрица!  + +- Так это ты, клубничный медвежонок! - деревянный мальчик открыл дверь.  + +- Так бы сразу и сказал. Когда бабулешки вернешь?  + +- Не нуди, Пиноккио, - Винни-пух был почему-то мрачен. - Есть что накатить?  + +- На вот полбанки табуретовки, для себя берег, на опохмелку.  + + + +- Даже немножечко, чайная ложечка, это уже хорошо! - задвинул любимый тост Пух и замахнул.  + +- А где же твой свиноподобный корефан? - поинтересовался Буратино.  + +- В этом-то вся и проблема, братуха. Свинья - она и в Африке свинья. Вот ты как к измене относишься?  + +- Да мы третью неделю на ней сидим. Самоделкина закрыли на днях. Насмотрелся "Бойцовского клуба" и замутил выпуск пластита в домашних условиях. Нас всех в отдел тягают сейчас...  + +- Ты не догоняешь, дубовенький ты мой - Это когда друг уходит, понимаешь?  + +- Уходит? Ну так догони его и пристрели, как собаку!  + +- Я че, по-твоему, дятел Вуди Вудпеккер? Нет его нигде, а то давно бы порешил.  + +- У Белоснежки был?  + +- Да уж конечно, - Пух косел. - Опять групповой секс с элементами садо-мазо и некрофилии и ничего нового. Тебя, кстати, приглашали...  + +- Я свой гвоздь не на помойке нашел, - отмахнулся Буратино, - а пятачка искать вместе будем. Аллигатор, па-а-дъем!!  + +Из-под лестницы выпал сине-зеленый Гена.  + +- Хелло, лох-несский монстр! - поприветствовал его Винни-Пух.  + +- Ты кого лохом назвал, гризли поролоновый? - взгляд крокодила был мутен, как воды Нила, откуда его выловили когда-то совсем крошечным крокодиленком. - Давайте быстро водки мне!  + +- Вот оно, лицо алкоголизма. - Буратино криво усмехнулся, затянувшись беломориной. - Накати, змей ты наш зеленый, да собирайся, по дороге расклад узнаешь...  + +...До Простоквашина добирались на трамвае. Пух и деревяшка держались молодцом, как будто трезвые, чего никак нельзя было сказать о Зеленой бестии, как его в такие моменты называли друзья.  + +- Голубой вагон бежит, качается! - затянул было Гена с глупой ухмылкой.  + +- Захлопни сундук, Годзилла, пока тебя тут голубым не сделали! - прошипел медведь.  + +- В голове опилки, да, да, да! - не унимался крокодил.  + +- Залепи дуло, Паваротти хренов!  + +- Приехали, выходим. - Буратино помог рептилии сойти с помощью маваши-гири...  + +- Кто там, в натуре? - галчонок, как всегда, тусовался у двери, осуществляя пропускной режим.  + +- Кто-кто. Почтальон Печкин, принес пол-литра для вашего мальчика! -съязвил Винни, которого слегка укачало в трамвае.  + +- А вот не надо ля-ля! Я уже давно как туточки! - Печкин, появившийся в проеме двери, выглядел оригинально. Кружевные дамские панталоны и ковбойская шляпа - немного неожиданно для сельского почтальона.  + +- А ты, я смотрю, все марочками увлекаешься, - хихикнул понимающе Буратино.  + +- Ра-бо-та у нас та-кая! - фальшиво пропел почтальон и рухнул, как подрубленный, строго в направлении севера. Внутренний компас его еще никогда не подводил. Перешагнув через безжизненное тело, герои вошли внутрь.  + +- Матроскин! Матроскин, мазафака!!! Где ты, отзовись!  + +- Да здесь я, под диван закатился. Ништяк, что вы приехали, пацаны! Теперь мы втрое больше сена для моей коровки заготовим!  + +- Мы по делу, - буркнул Винни. - Ты Пятачка не видел?  + +- Я ему давеча брякнул, что свиней хочу развести, так он и не появляется. Может, он не догнал чего? А в чем рамс?  + +- Да покрылся, гад, а куда - неизвестно. Мы это, Шарика возьмем с собой, он тут нюхом исключительным хвалился. С охотничьей собакой мы его влет возьмем.  + +- Ой, а он тоже куда-то запропастился, - стыдливо отвел глаза в сторону Матроскин. - Шашлычка не хотите?  + +- А самогоныч к шашлыкам есть? - встрял Гена. - Спрашиваешь...  + + + +На малину к Карабасу-Барабасу Матроскин, Буратино и Винни-Пух поехали уже без крокодила. Зеленый барагоз перекрылся и был оставлен спать валетом с Печкиным на раскладушке. Банда Барабаса как раз обмывала успешную премьеру, состоявшуюся в Местном ювелирном магазине. Сидевшие за столом Дуремар, Бармалей, Весельчак У и другие члены преступной группировки не удивились неожиданному визиту подвыпившей братвы.  + + + +- Ну а где же наш хороший, наш любимый крокодил? - поинтересовался Шер-Хан.  + +- Он с Тотошей и Кокошей с нами по делу проходил, - пояснил он тем, кто был не при делах.  + +- Нужен он тебе больно, шурин? Это ж катастрофа ходячая, - встрял Матроскин. - Скажите лучше, вы нашего поросенка Порки не видали?  + +- Поросенок тут и не отсвечивал! А вот этого молодого человека, деревянного по жизни, я сейчас, в натуре, на растопку пущу! - с ходу забуровил Барабас.  + +- Кто старое помянет, тот лох! - заявил Буратино. - А если тебя этот золотой ключик так парит, то я тебе за него бабками отдам.  + +- Да у меня такими ключами весь гараж завален! Тут дело в уважении. А ты меня прокинул, как фраера позорного. Где мой кукольный театр?  + +- Слушай, Карабас. Ты не прав. Я тебя уважаю, но и ты меня пойми. Не я твоих шестерок разогнал, ты их сам не удержал. А эту истеричку Мальвину можешь хоть завтра забирать, она у меня уже в почках сидит. И вообще, давай на базар серьезный позже пойдем... У нас пацан пропал, а ты мужчина реальный, со связями. К кому еще пойти, как не к тебе?  + +- Ладно, - поостыл малость Карабас. - Милости прошу к нашему шалашу. Балдейте пока, а я прозвон по точкам совершу.  + + + +Пока гопка расслаблялась, Борода срастил все темы.  + + + +- Нет нигде вашего свинопотама! Но есть вам смысл навестить трех подсвинков. Может, они в курсах...  + +- Ну, на дорожку. На святое дело идем, товарища из беды выручать, - двинул телегу Винни-Пух. Накатив все вместе по двести, троица направилась к выходу.  + +- Кто такие?- хрюкнул Ниф-Ниф в ответ на стук в дверь.  + +- Это мы... мы... мылиция, - промычал в ответ не совсем адекватный кот.  + +- А дома никого нет!  + +- А кто говорит?  + +- Вы говорите с автоответчиком. Просьба оставить свое сообщение после звукового сигнала. Хрю-ю-ю, хрю-ю-ю...  + +- Слушай, ты, автоминетчик, че ты тут цирк устроил. Это я, Винни, открывай!  + + + +Дверь открылась. Посреди комнаты валялся связанный волк - его вид очень развеселил друзей.  + + + +- За что вы его так?  + +- Посягал на частную собственность, - заявил Нуф-Нуф.  + +- Гражданин Волк, за порчу недвижимости...  + +- И Красной Шапочки! - закричала из кровати Красная Шапочка.  + +- ...и Красной Шапочки, - продолжил Нуф-Нуф, - вы приговариваетесь к съедению! Ха-ха-ха!!! Видел бы нас сейчас Учитель, Ганнибал Лектор, бог свиней-волкоедов!  + +- Я исправлюсь! - истошно завопил Волк, но его никто не слушал.  + +Наф-наф сосредоточенно натачивал тесак мясника, похрюкивая себе под нос "Нам не страшен серый волк, серый волк, серый волк...".  + +- Наф-Наф, брателло, вы Пятачка не видели?  + +- Я с этим выродком не общаюсь. Он мне вообще не родной. Слышь, Матроскин, ты волчатину готовить умеешь?  + +- Не-а, только собачатину, - кот покраснел.  + +- Какая разница - волчатина, собачатина... Тот же болт, только в левой руке.  + +- Не, в другой раз. Мы Пятачка ищем. Из угла раздалось жалобное шипение.  + +- Я злой и страшный серый волк, я в поросятах знаю толк...  + +- Серьезно что ли? - ухмыльнулся Наф-Наф.  + +- Говори, что про Пятачка знаешь, - встрепенулся Винни.  + +- А ВОТ ЧТО!!! - зарычал яростно волк, разрывая наручники. - Умрите! Умрите, грязные свиньи!!!  + +- Делаем ноги, пацаны, - шустро среагировал Буратино, - Пятачка здесь нет!  + + + +Перевести дух удалось лишь на крыше, у домика, где жил Карлсон. У того было не заперто. Карлсон и Чебурашка, как ни в чем не бывало, резались в очко на спине проигравшегося в пух и прах Малыша.  + +Протрезвевшие пятачкоискатели своим появлением нарушили мирный ход игры.  + +- Здорово, бродяги! Что, пришли к лучшему в мире Карлсону? А почему без подарков? А ты, шкура плюшевая, кстати, должен мне три банки меда.  + +- С какого это?..  + +- А с такого! Типа, не помнишь, как ты со мной спорил на три банки меда, что я не съем три банки меда? Проспорил, гони. Или уматывай отсюда.  + +- Потом разберетесь. Карлсон, к тебе поросенок не забегал?  + +- Который из них?  + +- Да тут один и остался, да и то теоретически - Пятачок.  + +- А остальных что, Волк сожрал? - прикололся Карлсон.  + +- Тут ты тупо угадал, Пропеллерхэд! И Красную Шапочку тоже!  + +- О, Красная Шапочка, любовь моя! - зарыдал Чебурашка. - А Гены, Гены там не было?  + +- Твой сперматозавр сам кого угодно сожрет - не прокормишь. И где его искать, мы знаем, а вот куда Пятачила делся - вопрос! Давай без байды, видел его или нет?  + +- А ты видел на входе табличку "Склад потерянных свиней"? - заорал Карлсон.  + +- После того как это свиномордие скинуло мне под видом шарика дырявый презик, я и сам его ищу!  + +- Может он съел чего? - наивно вклинился в разговор Малыш.  + +- Ты же сам, Винни, его грибами давеча кормил...  + + + +В лесу было накурено. Утомленные поисками Пятачка, кореша уныло брели по дороге, вымощенной желтым кирпичом. Далеко-далеко на луну завывал людоед.  + +- А может это он его... Того... этого?  + +- Так он и не скулил бы тогда, как побитая шлюха. Что делать-то будем?  + +- А че делать? Бухать будем, - брякнул усталый Винни-Пух.  + +- Пойдем ко мне, братва. Меня мама медовушкой подогрела...  + +- Ну, помянем раба божьего Пятачка. Славный был парень, хоть и свинья, - задвинул Буратино.  + +- А я его частенько поколачивал, - смахнул скупую слезинку Пух.  + +- Ты бросил меня, ты бросил меня, - затянул Матроскин и все подхватили.  + +- Когда ты ушел, я осталась одна!!!  + +- Сидим как-то по-сиротски, - издалека начал Буратино.  + +- Пух, у тебя, кроме сечки, нет никакой закуси? А то она мне еще с армии не лезет! Когда служил дуболомом у Урфина Джюса - только ею и кормили.  + +- Сейчас гляну чего-нибудь в подполе, - приподнялся нетвердо Пух.  + +- Где-то там Пятачок полбатона Докторской закурковал. Ему она уже ни к чему. Медвежонок открыл люк и спустился в подвал.  + +- Фу, как здесь воняет! Босота, посветите-ка мне, я, кажется, во что-то вляпался! Буратино просунул вниз руку с зажженной свечой. Взгляду нетрезвого Винни-Пуха предстало душераздирающее зрелище. В луже собственной Блевотины возлежал Пятачок, собственной персоной. Он сжимал в зубах огрызок недоеденной Докторской и тихо повизгивал во сне, дергая ножкой.  + +- Пятачок! Пацаны, это же Пятачок! - радостно заорал Винни. - Ты живой, дружище!!!  + +- Нет, мертвый, - прохрипел поросенок, сдувая прилипший к щеке бычок. – В глаза не свети, придурок.  + +Извлеченный на свет Пятачок первым делом потребовал водки, а потом Поведал свою грустную историю.  + +- Да че тут рассказывать-то. Лезу я, значит, за колбасой. А тут, гляжу, под лестницей банка. Помнишь, Винни, мы брагу на опилках ставили?  + +- А я думал, мы ее выпили давно. Брага... очень уж хитрый предмет.  + +- И очень жестокий, - добавил Пятачок. - Помню, решил попробовать... а тут и вы пришли. Гулянка продолжилась с новой силой. Теперь уже за здравие воскресшего из мертвых. Под стол попадали только к утру... diff --git a/personal/creative/humor/Вестник Средневековья.md b/personal/creative/humor/Вестник Средневековья.md new file mode 100644 index 00000000..55949051 --- /dev/null +++ b/personal/creative/humor/Вестник Средневековья.md @@ -0,0 +1,325 @@ +Есть мнение, что цивилизация развивается циклически, т.е. то, о чём говорят сейчас,  + + + +обсуждалось и раньше, и ещё раньше, и много раньше того времени. Понятное дело, что в  + + + +настоящее время способов получить информацию гораздо больше, чем, скажем, в Средние  + + + +Века. Если вспомнить о том, что основные новости люди узнавали в то время на ярмарках,  + + + +то это можно представить так:  + +средневековый город, рыночная площадь, толпа народа, на высоком помосте стоит  + + + +глашатай. Часы на городской ратуше отбивают сколько-то там времени, глашатай трубит в  + + + +рог, разворачивает свиток и громким голосом объявляет:  + +- почтенная публика, прошу минуту вашего внимания! В эфире еженедельная  + + + +информационно-аналитическая программа "Вестник Средневековья":  + + + +новости высоких технологий: на ярмарке в Ганновере было представлено новое поколение  + + + +абаков - счёты. Данная модель отличается более дружелюбным интерфейсом. Кроме того, на  + + + +счётах можно применять программное обеспечение с открытым кодом - арабские цифры.  + + + +Вообще, аналитики прогнозируют снижение рыночной доли, занимаемой римскими цифрами.  + +В тоже время интересная ситуация складывается и с монополизмом в области алфавита - к  + + + +латинице уже вышло столько локализаций, что компания-разработчик просто не в состоянии  + + + +оперативно отслеживать все изменения и выпускать новые манускрипты с правилами письма  + + + +и чтения. Растёт влияние кириллицы, арабской вязи и иероглифического письма. Правда, с  + + + +последним интересный момент - покуда компания-разработчик не решит проблему с  + + + +кодировкой, особой популярности данного вида письма на западном рынке ожидать не  + + + +приходится. Тем не менее, компания активно захватывает соседние ниши на рынке - в  + + + +частности, нишу, связанную со средствами хранения данных. Бумага, столь интенсивно  + + + +продвигаемая в качестве заменителя пергамента, рассчитана на потребителя с небольшим  + + + +доходом. На международной выставке-ярмарке "Шёлковый Путь" было представлено  + + + +оригинальное решение в области хранения данных - книга в переплёте. Это ещё один удар  + + + +по производителям пергамента.  + + + + + + + +новинки связи: на последней ярмарке в Лионе была представлена новая порода почтовых  + + + +голубей. Эту породу от прочих конкурентов выгодно отличают стильная белая тушка,  + + + +изящные крылья, точёные лапки, блестящие глаза и потрясающие хвостовые перья. Голуби в  + + + +современном мире - это идеальный способ для отправки коротких текстовых сообщений.  + + + +Очевидно, что время посыльных и гонцов безвозвратно уходит в прошлое.  + +"Голуби connecting people". Спрашивайте в лучших голубятнях вашего графства.  + + + +новости транспорта: во время последнего крестового похода на Иерусалим делегация  + + + +нашего княжества посетила крупный региональный конный салон. Особый ажиотаж наблюдался  + + + +на стенде арабских скакунов. Как было указано в пресс-релизе, новейшая модель в  + + + +базовой комплектации (сбруя, попона, седло) стоит полторы сотни золотых.  + + + +Дополнительный пакет (легкосплавные подковы, тюнинговая уздечка, стрижка гривы и  + + + +хвоста) обойдётся ещё в сумму порядка 20-30 золотых. Кони этой породы отличаются  + + + +большим запасом пробега и низким расходом топлива - всего стог сена и мешок овса на  + + + +неделю эксплуатации. Ведутся переговоры о поставке крупной партии скакунов на  + + + +европейский рынок.  + + + +обзор рынка недвижимости: стоимость участков под застройку в районе "Стоунхендж"  + + + +падает в связи с его удалённостью от городской инфраструктуры. Резко подорожали  + + + +земельные участки на восточной оконечности о. Сите в Париже - по слухам, там  + + + +начинается строительство современного культурного центра с большим концертным залом,  + + + +галереей современного искусства и обширной библиотекой.  + +Средняя цена предложения замковой недвижимости такова: в Болонии за пятибашенный замок  + + + +просят 1000-2000 золотых за акр, в Вестфалии и Лотарингии цена за акр трёхбашенного  + + + +замка составляет порядка 800-900 золотых, двухбашенного - 350 золотых. Элитные  + + + +многобашенные замки на берегах Темзы и Сены подскочили в цене на целых 500 золотых за  + + + +акр и в среднем стоят по 50-60 тысяч золотых за весь замковый комплекс.  + + + +новости кино: нашумевший блокбастер "Игорь против Половца" продолжает бить рекорды по  + + + +кассовым сборам. Готовится увидеть свет манускрипт по мотивам этой постановки. Если вы  + + + +хотите, чтобы у вашего менестреля был самый модный в этом сезоне полифонический  + + + +рингтон "Плач Ярославны", обращайтесь в ближайшее аббатство. Звонить в колокол  + + + +3-5-5-3. Звонок платный. О стоимости звонка узнавайте у привратника.  + + + +новости культуры: после необычайного успеха в Китае и Корее перспективная панк-группа  + + + +из Монголии "Чингиз-Хан" планирует мировое турне по странам Средней Азии и Закавказья.  + + + +В настоящее время группа находится на Руси и как передаёт отуда наш специальный  + + + +корреспондент Нестор Трублоггер, устроила прямо-таки дикий отжиг на вчерашнем концерте  + + + +в Киеве. В блиц-интервью лидер панк-группы заявил, что намерен также дать концерты в  + + + +Венгрии, Болгарии и, возможно, Северной Италии.  + + + +раздел "курьёзы": в ответ на выход книги Дж. Бруно "Таки она вертится" инквизицией был  + + + +спровоцирован флешмоб, достигший в настоящее время небывалого размаха. Жилище писателя  + + + +прямо-таки завалено анонимными булыжниками с надписью "аццкий сотона", "ниасилил" и  + + + +"фтопку". Интервью у писателя взять не удалось.  + + + +новости одной строкой: в Амстердаме легализовали шпанскую мушку.  + + + +объявление: крупной стабильно работающей компании с иностранным капиталом требуется  + + + +специалист на должность топ-феодала. Требования к кандидату: знание системы сбора  + + + +податей, опыт работы с дружинами, навыки руководства вассалами, успешные выступления  + + + +на рыцарских поединках. Проживанием (замок), служебным транспортом (лошадь),  + + + +обслуживающим персоналом (челядь), одеждой (латы, оружие) и визитками (щитами с  + + + +корпоративной символикой) обеспечиваем. Обращаться во дворец, спросить короля.  + + + +новости спорта: не так давно в товарищеском матче по хоккею на Ладожском зимнем дворце  + + + +спорта встретились сборная русичей и сборная тевтонцев. В итоге "Русичи" победили  + + + +"Тевтонцев" с разгромным счётом. На послематчевой пресс-конференции главный тренер  + + + +тевтонцев заявил, что игра показала также несостоятельность его команды и в таком виде  + + + +спорта как водное поло. "Экипировка наших игроков никуда не годится. Мы вынуждены  + + + +искать нового генерального спонсора нашей команды" - сказал в частности главный тренер  + + + +"Тевтонцев".  + + + +и в заключение о погоде: прогноз на ближайшее время мы, к сожалению, дать не в  + + + +состоянии, поскольку придворный звездочёт погиб во время последних опытов по получению  + + + +философского камня. Рекомендуем просто одеваться по сезону.  + +Оставайтесь с нами! С уважением, информационное агентство средневековых новостей. \ No newline at end of file diff --git a/personal/creative/humor/Киношаблоны.md b/personal/creative/humor/Киношаблоны.md new file mode 100644 index 00000000..b78effb2 --- /dev/null +++ b/personal/creative/humor/Киношаблоны.md @@ -0,0 +1,2936 @@ + +  Разместил zombak в разделе Лучшее из архива (дата: 2006-06-04 время: 21:38:07) + + + +- А -  + + + +Автомобиль  + + + +1. Если герой едет за рулем автомобиля даже по идеально ровной дороге, ему все равно приходится яростно крутить руль в разные стороны.  + + + +2. Даже в самом центре многомиллионного города, у героя всегда есть возможность припарковать машину прямо перед тем зданием, в которое ему нужно попасть.  + + + +3. При стрельбе по машинам все пули попадают в двери. В этом случае все остаются живыми. Стекла бьются только при стрельбе сзади, причем только заднее. Даже в случае промаха в водителя, лобовое остается целым. Видимо, чтобы укрыться от пуль, достаточно спрятаться за дверцу машины.  + + + +4. Перед машиной в которую врезаются полицейские или бандиты всегда установлен трамплин. Взлетевшая машина всегда падает на крышу. Обязательно загорается, но взрывается только после того как положительные персонажи спасены (если там были положительные персонажи). Бандиты взрываются мгновенно.  + + + +5. Хорошо отомстить злодею можно, передавив на грободобном грузовике его безумно дорогие машины.  + + + +Автокатострофа  + + + +1. Столкнувшиеся машины всегда сразу взрываются и начинают гореть.  + + + +2. Если во время столкновения в машине сидит Герой, а в багажнике лежит достаточно взрывчатки, чтобы взорвать Вашингтон (вариант: Ядерная или Водородная Бомба), то взрывчатка не взрывается. Если же в машине со взрывчаткой сидит Антигерой, машина благополучно взрывается.  + + + +Исключение 1: в начале фильма Антигерой не может погибнуть ни при каких обстоятельствах, иначе с кем же бороться Герою.  + + + +Исключение 2: машина с Антигероем взрывается, но он выживает и начинает мстить Герою (независимо от того, кто его взорвал).  + + + +Исключение к исключению 2: машина с Антигероем взрывается и он погибает, после чего герою начинает мстить брат (сын, дочь, папа, мама, любимая болонка Антигероя) Антигероя, независимо от того, кто взорвал Антигероя.  + + + +Алкоголь  + + + +1. Полицейский от которого ушла жена с детьми обычно пьёт много виски (в последнее время всё большую популярность набирает водка). Из белой горячки его выводит сообщение о том, что его брат (сестра, дочь, жена, мать, отец и др. родственники) жестоко убиты, вследствие чего он свирепеет и начинает убивать нехороших людей. Так он становится Героем.  + + + + + + + +Исключение: полицейский начинает убивать хороших людей и становится Антигероем.  + + + + + + + +Американцы  + + + +1. Американцы - это лучшая часть человечества (по крайней мере, если судить по американским фильмам). Именно американцы победили фашистов, причем сделали это не по необходимости самосохранения, а исключительно из сострадания к остальному миру. Это распространяется не только на фашистов (см. п.2).  + + + + + + + +Исключение: Русские (советские), а также, индийские фильмы (причем здесь индийские? а где вы видели фашистов в индийских фильмах?)  + + + + + + + +2. Именно американцы всегда спасают мир от русских, арабов, инопланетян, астероидов, динозавров, маньяков, стихийных бедствий... всего, что можно только представить. Иногда они спасают мир даже от самих себя - американцы настолько самодостаточны, что нередко отказываются импортировать извне даже зло.  + + + + + + + +3. Главный герой всегда американец. см. Герой.  + + + + + + + +Исключение: Исторические фильмы о средних веках, Римской империи, древнем Египте и Шекспире, а также, не американские фильмы.  + + + + + + + +4. Американцы есть в каждой стране и находятся они там для того, чтобы страдать по любви к родине, мечтать о гамбургерах, кетчупе и кока-коле и, главное, наглядно демонстрировать зрителю как безнадежно отстал весь остальной мир от Америки в своем развитии.  + + + + + + + +Исключение: Американские преступники, сбежавшие в Мексику.  + + + + + + + +5. В американских исторических фильмах, частенько попадаются среди персонажей чернокожие. И не просто африканцы, а именно американские <нечистые> негры-полукровки, с американскими повадками. Это необходимо чтобы хоть как-нибудь обозначить американское <присутствие> во всех крупных исторических событиях.  + + + + + + + +См. также Афроамерканцы  + + + + + + + +Англичане  + + + +Англичане чопорны, смешны и всегда носят галстуки.  + + + + + + + +Антигерой  + + + +1. Антигерой носит кожаный плащ и стильные шелковые сорочки, разумеется, черного цвета. Любит классическую музыку - оперу, балет и дорогой коньяк.  + + + + + + + +2. Когда антигерой вот-вот свершит что-то очень ужасное, за долю секунды его убивают наповал, причем в такой позе, к которой нужно готовиться секунд 40, и убить может кто угодно, вплоть до 6-ти летней девочки, невесть откуда взявшей оружие, и лицо у нее обязательно будет такое, как будто она это делает каждый день.  + + + + + + + +3. При борьбе со злым, международным террористом, лучшее оружие - сарказм и едкие замечания. Непонятно каким образом, но это приводит к героя победе, даже если он находится в самом безвыходном положении.  + + + + + + + +4. Перед тем, как выпустить пулю в героя, злодей обязательно несколько минут читает ему нотацию, чтобы герой мог придумать какую-нибудь пакость во избежание неминуемой смерти.  + + + + + + + +5. Злодеи имеют такие злобные физиономии, что их надо арестовывать только по внешнему виду.  + + + + + + + +6. У Антигероя может быть абсолютно положительный родной брат, которого Антигерой ненавидит за его доброту. Если фильм в жанре фэнтэзи, несчастный брат Антигероя нередко влачит свои жалкие дни в заброшенной камере подземелья.  + + + + + + + +7. Если Антигерой громко и уверенно утверждает, что он непобедим, значит жить ему осталось считанные секунды.  + + + + + + + +Далее см. Герой  + + + + + + + +Армия Злого Властелина  + + + +1. Армия Злого Властелина одета так, чтобы выглядеть максимально угрожающе но, при этом, терпеть максимальные неудобства. Например, непрозрачные громоздкие шлемы (когда лица не видно - страшнее, не правда ли?) или какие-то нагромождения в районе плечевого пояса (чтобы подчеркнуть мужественность и физическую силу).  + + + + + + + +Афроамерканцы см. Негры  + + + + + + + +- Б -  + + + + + + + +База данных  + + + +Антигерой всегда пользуется современнейшими методами и средствами поиска данных, и имеет доступ к любым базам данных ФБР и полиции. Герой обычно пользуется бумажными справочниками, или по работе у него не хватает полномочий для доступа к компьютерной базе. В этом случае ему всегда поможет милая девушка за компьютером, за обещание сходить пообедать, или как-нибудь переспать с ней, даже если перед этим, герой ее неоднократно обманывал.  + + + + + + + +Бар  + + + +1. Стойка бара обладает пулеотталкивающим действием. При стрельбе по спрятавшемуся за стойкой, все пули попадают в посуду, стоящую на ней, или в бутылки на витрине. Посуда и бутылки обладают таким же притягивающим пули свойством, как и значки с портсигарами. См. Пули.  + + + + + + + +2. Антигерой в баре начинает драку предварительно перевернув, или сломав столик. Чаще это оказывается тот, за которым он сидит.  + + + + + + + +3. Если в баре начинается драка, финальный аккорд будет поставлен ударом бутылки по голове.  + + + + + + + +4. Если драка затевается с применением восточных единоборств, то в баре обязательно окажется бильярд.  + + + + + + + +5. Если в баре есть бильярд, то все, кто в него играет участвуют в драке на стороне антигероя.  + + + + + + + +6. Если в бар заходит герой, то все обязательно на него обернутся. На антигероев никто не обращает внимания. Видимо, так и становятся антигероями.  + + + + + + + +7. Почти у каждого антигероя есть свой бар (казино, клуб), или офис в баре (казино, клубе), где героя обязательно побьют в первый его приход, а во второй он устроит там погром сам. При этом или антигероя на месте не окажется, или он спустится к нему сам с улыбкой на лице.  + + + + + + + +Бензопила  + + + +Бензопила - это один из тех предметов, который всегда оказывается под рукой в нужный момент.  + + + + + + + +Бомба  + + + +1. Подложенная в машину или здание бомба никогда не взорвется, если туда зашли: группа школьников, учащихся младших классов, беременная женщина, кормящая мать с грудным ребенком, собачка.  + + + + + + + +2. Любая бомба с часовым механизмом оснащена индикатором с большими красными цифрами, чтобы все могли увидеть, сколько осталось времени до взрыва.  + + + + + + + +3. Любой человек может обезвредить бомбу с часовым механизмом, так как интуитивно знает какой именно проводок нужно выдернуть из кучи других, причем, сделать это он может только при одном условии: за максимум три секунды до запрограмированного взрыва.  + + + + + + + +4. Злодеи любят проверять свои бомбы, когда таймер показывает 00:0... с чем-то.  + + + + + + + +Бомба Ядерная  + + + +Ядерная бомба (варианты: контейнер с вирусом, генетически измененная такса-убийца) обычно спрятаны за стеклянной дверью со сверхсложным трехзначным кодом. Специально дверь может быть снабжена детектором отпечатка пальца (варианты: сканером сетчатки, датчиком запаха пота Лица с Доступом), однако никто не мешает потенциальным террористам отрезать у Лица палец (варианты: глаз, подмышку) и принести с собой в пакете с гамбургером.  + + + + + + + +Бронежилет  + + + +Стандартный бронежилет может выдержать попадание из безоткатного орудия, либо может быть пробит алюминиевой вилкой, в зависимости от того, на ком он был надет.  + + + + + + + +- В -  + + + +Ванная комната  + + + +1. Если герой прячется от преследователей в ванной комнате (туалете), то в ней всегда имеется окно, через которое герою легко удается сбежать.  + + + + + + + +2. Если в триллере женщина открывает шкафчик с зеркалом на дверце, то после того как она его закроет, в зеркале обязательно появится лицо маньяка или убийцы.  + + + + + + + +3. Щеколда в ванной самая прочная часть американского дома. Даже если дверь открывается во внутрь, сломать дверь (разбить ножом или топором), оказывается гораздо проще, чем ее выбить.  + + + + + + + +Вентиляция  + + + +Система вентиляции любого небоскреба - самое лучшее место для того, чтобы спрятаться. Никому и в голову не придет искать героя там, зато по ней можно беспрепятственно попасть в любую часть здания.  + + + + + + + +Вещества  + + + +Жидкие вещества в кино имеют свои четко определенные цвета, например - кислота или яд обладают зеленые, а лекарство в пробирке - голубое.  + + + + + + + +Взрыв  + + + +В последние годы, лучший вариант спастись от взрыва - красиво прыгнуть метров на 10. Естественно в самый последний момент. Естественно, ни одной царапины или ушиба.  + + + + + + + +Враги  + + + +Не страшно, если герой подвергся нападению сил противника, во много раз превосходящих его численностью. Все равно враги будут нападать на героя по одному, а остальные будут терпеливо ждать своей очереди, окружив его со всех сторон и совершая угрожающие телодвижения.  + + + + + + + +- Г -  + + + + + + + +Газеты  + + + +Большинство людей хранит альбом с газетными вырезками, особенно когда их семья или друзья погибли в странных несчастных случаях при катании на лодках.  + + + + + + + +Герой  + + + +1. Если герою, допустим, необходимо выдать себя за офицера вермахта, то совсем необязательно говорить по немецки. Достаточно и немецкого акцента. Цвет кожи также не имеет значения.  + + + + + + + +2. У героя в подвале дома (в пустыне Гоби, в камере хранения на вокзале) имеется некоторое количество оружия, как правило, достаточное для вооружения двух-трех пехотных батальонов. Антигерой комплектует свой арсенал в супермаркете...  + + + + + + + +3. В конце фильма, герой отправляется на рискованную встречу с врагами только один и без оружия.  + + + + + + + +4. Герой носит синие джинсы и мятые светлые рубашки (майки), антигерой - кожанный плащ и стильные шелковые сорочки, разумеется, черного цвета. Герой любит Брюса Спрингстина и пиво Miller, может выпить ящик виски и не быть пьяным. Антигерой любит классическую музыку - оперу, балет и дорогой коньяк. Резюме: герой - простой рабочий парень, антигерой - извращенный аристократ. Хочется спросить: "Чем советская идеология отличается от американской?"  + + + + + + + +5. Существенный фактор - имя: героя обычно зовут Джон (варианты: Джек, Том), антигероя обычно зовут как-то сложно или экзотично - Ахмед (варианты: Иван, Ли). Герой либо холост (варианты: вдовец воспитывающий прелестную дочь, просто безутешный вдовец), либо примерный семьянин. Антигерой никогда не женился и не собирается - у него и так хватает подружек.  + + + + + + + +6. Если герой оказывается беззащитен перед врагом (попал в ловушку, потерял оружие и т.д.), ему не стоит опасаться за свою жизнь! Враг ни за что не станет его убивать. Он будет терпеливо рассказывать герою все свои тайны (преимущественно то, за чем герой гонялся весь фильм), пока не придет кто-то, кто поможет герою и убьет врага.  + + + + + + + +7. Когда злодей вот-вот убьет героя, даже уже прицелился в него, то в этот момент обязательно кто-нибудь выстрелит из-за угла и убьет злодея. Как правило, это будет красавица, которая не только спасает жизнь, но под занавес отдастся герою.  + + + + + + + +8. Если главного героя враги взяли в плен, они никогда не убьют его сразу. Они сначала расскажут при нем все свои тайны, раскроют все свои секреты, выдадут шифры всех сейфов и коды всех компьютеров, для верности записав их на бумажку и положив на видное место, а затем закроют героя в шкафу и подожгут комнату зажигалкой Zippo, злорадно хохоча и находясь в полной уверенности, что герою не выбраться.  + + + + + + + +9. Как герой, так и антигерой, при всех их достоинствах и недостатках обладают оригинальным чувством юмора. Уметь шутить должны оба.  + + + + + + + +10. Герои боевиков никогда не наказываются за убийства или уголовно-наказуемые деяния, даже когда оптом отправляют на тот свет невинных прохожих или стирают с лица земли целые города.  + + + + + + + +11. Количество спасаемых героем человеческих жизней должно быть в пределах от 2 до 6 миллиардов.  + + + + + + + +12. В опасные для жизни моменты, рядом с героем находится специальный вентиль, повернув который можно обдать злодея мощной струей горячего пара.  + + + + + + + +13. Входя в стан врага, герой очень любит перемещаться какими-то сложными кувырками.  + + + + + + + +14. Если Героя подставили, за ним гонятся и плохие, и относительно хорошие (полиция, спецслужбы и т.д.), которых он мочит без разбора, попутно сперев кучу сверхсекретных документов (образцов оружия, вирусов и т.д.) чтобы доказать свою невинность, а также угоняет (разбивает, взрывает) тучу машин, самолётов, мелких лавочек, магазинов и частных домов, а в конце к Герою подходит Главный начальник и говорит приблизительно следущее: "Ты уж нас прости, мы-то де думали ты - плохой парень, а ты ничего - белый (чёрный) и пушистый, так что можешь теперь идти домой и спать спокойно."  + + + + + + + +15. В начале фильма Герой и Антигерой встречается, в результате чего Герою сильно бьют морду или сподвижники Антигероя, или он сам. В процессе фильма Герой становится все круче и круче и к финалу гасит всех сподвижников Антигероя и его самого.  + + + + + + + +Героиня  + + + +1. Оставшись на ночь в мрачном и загадочном доме и услышав странный шум, героиня идет узнать в чем дело, одев самое соблазнительное нижнее белье.  + + + + + + + +2.Героиня всегда просыпается по утрам со свежим макияжем и укладкой.  + + + + + + + +3.Губная помада никогда не стирается, даже если героиня ныряет с аквалангом, целуется взасос, ест сэндвичи с тунцом.  + + + + + + + +4. Если героиня работает в полиции, то она обязательно - либо в детстве подвергалась насилию со стороны отца, либо ее отец просто был полицейским и подвергал насилию других.  + + + + + + + +5. Чтобы решить исход финальной схватки, плененная героиня должна издать стон или писк. Это воодушевит главного героя и он сразу же победит в бою.  + + + + + + + +6. Если героиня - молодая и красивая женщина, то вам неизбежно покажут, как она моется в душе. Нередко, действие начинается именно тогда, когда героиня в душе (чаще всего это можно увидеть в фильмах ужасов). Общая площадь обнажённого тела Героини обратно пропорциональна её известности в Голливуде и ее возрасту (чем известнее или чем старше актриса, тем меньше она показывает).  + + + + + + + +Исключение: Иногда мы можем видеть тело молодой и "дорогой" актрисы в подробностях. Но это тело дублерши.  + + + + + + + +Грязь  + + + +Если персонаж попадает лицом в грязь (торт или тому подобное), то в следующем кадре эта самая грязь аккуратно наложена ему на лицо, благоразумно оставив открытыми участки вокруг глаз.  + + + + + + + +- Д -  + + + + + + + +Дверь  + + + +1. Это та разновидность препятствия, которая преодолевается положительным героем за доли секунды, а антигероем гораздо дольше.  + + + + + + + +2. В критической ситуации дверь ОБЯЗАТЕЛЬНО открывается только в последний момент.  + + + + + + + +3. Если действующие лица фильма проникают туда, куда им проникать не следует, то они очень не любят закрывать за собой дверь. Когда герой крадучись пробирается на некий секретный объект, то он роется в секретных архивах (ящиках стола, сейфах и т.д.), обязательно оставив за собой дверь распахнутой настежь (или хотя бы приоткрытой). А в это время, сквозь приоткрытую дверь красиво струится луч света.  + + + + + + + +Деньги  + + + +Расплачиваясь с таксистом, герои фильма никогда не заглядывают в кошелек, а достают купюру наугад, и это всегда оказывается нужная сумма.  + + + + + + + +Дети  + + + +Если от главного героя ушла жена, то она обязательно запретит детям встречаться с отцом.  + + + + + + + +- Ж -  + + + + + + + +Жена  + + + +1. Если главный герой целиком и полностью отдает себя работе, на которой еще и возникают постоянные проблемы, то от него обязательно уйдет жена.  + + + + + + + +2. Если главный герой решает проблемы, возникшие на работе, но престает уделять ей внимание. Жена возвращается. Резюме: чтобы сохранить семью, нужно забить на работу.  + + + + + + + +Жертва  + + + +1. Жертва, убегающая от маньяка, несется сломя голову, а убивец идет не спеша и все-таки настигает ее. Причем жертва может быть молодой, здоровой и прекрасно знать местность (например, завод, университет), а убивец быть полусгнившим трупом, впервые оказавшимся на этом месте.  + + + + + + + +2. Если жертва убегает от преследователя и оглядывается, то она непременно падает.  + + + + + + + +3. На пути жертвы постоянно попадаются закрытые двери, хотя 5 минут назад жертва (еще не став жертвой) свободно разгуливала по помещению.  + + + + + + + +4. Когда злодей входит в дом жертвы, последняя принимает душ за прозрачной занавеской.  + + + + + + + +5. Если жертвой оказывается милая девушка, в конце фильма герой обязательно с ней переспит. Если у нее при этом есть муж, или любовник, его или убьют, или он окажется трусом, подлецом, и.т.д., или будет заодно с антигероем.  + + + + + + + +6. Большинство Жертв - молодые и красивые женщины. Видимо, в каждом зрителе все-таки прячется маленький садист.  + + + + + + + +7. Если за жертвой гонятся на машине, то он (она) будет упорно бежать прямо по дороге, не сворачивая в сторону, пока злодей не догонит. Видимо, природная вежливость не позволяет жертве свернуть с дороги (например в лес) и заставить злодея выйти из машины.  + + + + + + + +8. Если жертва пытается скрыться от злодея на машине, то последняя обязательно не заведется. Иногда, после нескольких попыток машина трогается с места, но только после того, как злодей уже просунул руки в кабину машины. Естественно, стекло в машине почему-то всегда опущено.  + + + + + + + +- З -  + + + + + + + +Заброшенная фабрика или завод  + + + +Где-то рядом, всегда есть заброшенная фабрика или завод, ведь это идеальное место для выяснения отношений - противоборствующим сторонам будет удобно драться среди ржавых станков и груды металлолома.  + + + + + + + +Завод (см. Заброшенная фабрика или завод)  + + + + + + + +Замок  + + + +Любой замок можно легко открыть кредитной карточкой или скрепкой (которая всегда есть под рукой). См. Ключ.  + + + + + + + +Исключение: замок в горящем доме, где остался запертым маленький ребенок.  + + + + + + + +Засада  + + + +В засаде друг или партнёр главного героя всегда чихнёт в самый неподходящий момент.  + + + + + + + +Зомби  + + + +см. Монстры, подвид Трупообразные.  + + + + + + + +Зубы  + + + +1. У героев всегда отличные зубы, которые не могут пострадать даже от удара кувалдой в челюсть.  + + + + + + + +2. Отличные зубы почти у всех. Даже у средневековых крестьян.  + + + + + + + +Исключение: бомжи или особо грязные средневековые крестьяне.  + + + + + + + +- И -  + + + + + + + +Иностранцы  + + + +1. Иностранцы всегда предпочитают одевать национальную одежду именно тогда, когда они отправляются на деловую встречу, чтобы подписать многомиллионный контракт.  + + + + + + + +2. Иностранцы (неанглоязычные: китайцы, арабы, русские), даже оставшись одни, все равно предпочитают говорить между собой по-английски.  + + + + + + + +Исключение: китайцы, арабы, латиносы, (а в перспективе и русские) - последние годы могут представлять из себя и простых американцев, в том случае, если они по сюжету - американские граждане.  + + + + + + + +- К -  + + + + + + + +Кашель  + + + +Кашель обычно служит симптомом неизлечимой болезни.  + + + + + + + +Китайцы  + + + +Бывают только трех видов:  + + + +1. Изготовители китайской пищи.  + + + + + + + +2. Мастера восточных единоборств. Незнакомы с огнестрельным оружием.  + + + + + + + +3. Члены китайской мафии. Хорошо знакомы с огнестрельным оружием.  + + + + + + + +Исключение: см. Иностранцы.  + + + + + + + +Ключ  + + + +1. У положительного героя всегда есть ключ (отмычка, проволока, скрепка, шпилька) который откроет нужную дверь. Если ключа нет, см.2.  + + + + + + + +2. Если ключа нет - он обязательно найдется. Если главный Герой не смог найти ключ, см. 3.  + + + + + + + +3. Если герой так и не смог найти ключ - дверь легко выбивается ногой. Даже без разбега. Или пробивается кулаком. Видимо, дверь, это сложный механизм, реагирующий на наличие ключа у главного Героя. Как только дверь замечает отсутствие ключа у Героя, она меняет свою молекулярную структуру в пределах от фанеры до картона.  + + + + + + + +Компьютер  + + + +1. Герои фильмов чрезвычайно быстро набирают текст, причем никогда не пользуются при этом клавишей пробел.  + + + + + + + +2. Персонажи фильмов никогда не печатают с ошибками.  + + + + + + + +3. На любом мониторе буквы имеют размер в несколько сантиметров. Ведь главное, чтобы буквы видел зритель.  + + + + + + + +4. Все панели управления работают под напряжением в 1000 вольт и имеют вмонтированные взрывные устройства. Это нужно для того, чтобы в случае малейшей оплошности героев, взорваться как можно красивее.  + + + + + + + +5. О сбое компьютер вы узнаете по яркой вспышке, клубам дыма, фонтану искр и взрыву, который отбросит вас от компьютера на несколько метров.  + + + + + + + +6. Хакер способен взломать самую крутую защиту, угадав пароль со второго раза. При этом он не должен превышать рост в 1 метр 60 см, обязан носить очки и иметь очень низкий уровень общей физической подготовки.  + + + + + + + +Исключение: ламер  + + + + + + + +7. Вы можете обойти сообщение "Отказ в доступе" с помощью команды "игнорировать".  + + + + + + + +8. Если вы просматриваете файл, а его кто-то удаляет, то файл исчезает с экрана.  + + + + + + + +9. Когда персонажи смотрят на монитор, изображение настолько яркое, что проецируется на его лицо.  + + + + + + + +10. Любая дискета обладает свойствами автозапуска с последующей демонстрацией длительной мультимедийной презентации.  + + + + + + + +11. Чтоб уничтожить данные на компьютере достаточно выстрелить в монитор.  + + + + + + + +12. На дискету или CD можно записать вирус, который настолько крут, что ВЗРЫВАЕТ компьютер.  + + + + + + + +13. Пароль обычно состоит из имени, или даты рождения пользователя компьютера. Возможен вариант: Имя, или дата рождения его жены или дочки, фотография которых стоит рядом на столе, а данные обязательно написаны на обратной стороне этой фотографии.  + + + + + + + +14. У текстовых редакторов нет курсора.  + + + + + + + +15. Суперкомпьютеры, которые используют НАСА и ЦРУ, или другие правительственные учреждения, всегда имеют очень простой графический интерфейс. Если же графического интерфейса нет, то используются чрезвычайно мощная текстовая командная оболочка, понимающая литературный английский (такая оболочка предоставляет доступ к любой нужной информации, стоит что-нибудь набрать вроде "Получить доступ к секретным файлам" на первой попавшейся клавиатуре).  + + + + + + + +16. Для того, чтобы заразить компьютер разрушительным вирусом, достаточно просто набрать на клавиатуре "Загрузить вирус".  + + + + + + + +17. Все компьютеры соединены.  + + + + + + + +18. После набора текста компьютер можно просто выключить, не сохранив данные.  + + + + + + + +19. Любой компьютер загружается не более, чем за 2 секунды.  + + + + + + + +20. Сложные вычисления и загрузка больших объемов данных завершаются не более, чем за 3 секунды.  + + + + + + + +21. Модемы в фильмах обычно передают данные со скоростью не менее 2 гигабайт в секунду.  + + + + + + + +22. Когда перегревается главный компьютер атомной станции или ракетной базы, все панели управления взрываются - непосредственно перед взрывом всего здания.  + + + + + + + +23. Если на дискете есть зашифрованные файлы, то стоит вам вставить ее в дисковод, и у вас сразу запросят пароль.  + + + + + + + +24. Компьютеры могут обмениваться информацией друг с другом независимо от того, кто их изготовил и в какой галактике (фильм "День Независимости"). Все системы имеют один и тот же стандартный интерфейс. Так, с помощью записной книжки вы можете подобрать PIN-код кредитной карточки (фильм "Терминатор-2").  + + + + + + + +25. Любые дискеты читаются на любом компьютере, оснащенном дисководом, любые программы работают на любой платформе.  + + + + + + + +26. Чем совершение компьютер, тем больше у него кнопок. При этом работа на таком компьютере требовала бы весьма профессионального оператора, так как на кнопках нет никаких надписей, за исключением кнопки "Самоуничтожение".  + + + + + + + +27. Большинство компьютеров, даже самых маленькие, способны работать в режиме воспроизведения реалистичной трехмерной интерактивной анимации в гигантских разрешениях с фотореалистичной глубиной цвета.  + + + + + + + +28. Лаптопы всегда могут работать в режиме полноэкранного видеофона в режиме реального времени и имеют неимоверную производительность.  + + + + + + + +29. Поиск в Интернете всегда дает вам именно то, что вы искали, независимо от того, насколько общие ключевые слова вы задали (так, например, в фильме "Миссия Невыполнима" Том Круз задал поиск по ключевым словам "файл" и "компьютер", после чего получил 3 ссылки).  + + + + + + + +30. Большинство лаптопов достаточно мощны, чтобы вклиниться в системы связи любых инопланетных захватчиков.  + + + + + + + +31. Любая компьютерная программа, даже на самых старых машинах 80-х, обладает интеллектом превосходящим интеллект ее пользователя, и способна отвечать на вопросы любой сложности, причем однозначно.  + + + + + + + +32. Большинство компов в фильмах - ноутбуки Apple, в следствие чего можно часто наблюдать надкушенное яблоко.  + + + + + + + +Исключение: компьютеры в ракетных шахтах, лабораториях и прочих стационарных установках, которые необходимо захватить Антигерою и освободить Герою.  + + + + + + + +Кровь  + + + +1. Кровь у героя может течь, откуда угодно и сколько угодно.  + + + + + + + +Исключение: Кровь у героини (см. 2).  + + + + + + + +2. Кровь у героини может выступить только в самом малом количестве, и она не должна находиться в районе таза (иначе, зритель может неправильно трактовать наличие крови).  + + + + + + + +Кока-Кола  + + + +Напиток, который пьют герои только тогда, когда им нужно сохранить бдительность, находясь в сомнительном заведении.  + + + + + + + +Исключение: если компания Кока-Кола вложила в фильм определенную сумму.  + + + + + + + +Кровавая Битва  + + + +В самой страшной и кровавой битве может выжить только тот, кто ни за что и никому не покажет перед сражением фотографию своей любимой, или ни под каким предлогом не поведает окружающим о том, чем он намерен заняться, когда все закончится. Если ты строишь планы на будущее - ты труп. Но, если ты главный герой - ты можешь себе это позволить.  + + + + + + + +Кровать  + + + +1. Этот предмет появляется в поле зрения зрителей только тогда, когда герои - либо вступают в половую связь, либо их насильственным способом заставляют прекратить не только эту связь, но и жизнь. Если герою или антигерою позарез нужно где-то убить женщину, лучшего места, чем кровать, просто не найти . Даже, будучи мертвым, женское голое тело, распластанное на постели, вызывает приятные эмоции.  + + + + + + + +2. На каждой кровати лежит L-образное одеяло, закрывающее женщину до подбородка, а мужчину только до пояса. Пожалуй, это тот штамп, который уходит в прошлое. Все чаще и чаще мы можем наблюдать постельную жизнь героев в подробностях.  + + + + + + + +Кокаин  + + + +1. Кокаин употребляется от 100 граммов за один нюх. Основная функция кокаина - абсолютный разрушитель семейных ценностей.  + + + + + + + +Исключение: Нет исключения.  + + + + + + + +2. Кокаин перевозят партиями не менее чем по 50 тонн. Ну, а чего мелочиться-то?  + + + + + + + +Кулак  + + + +1.Если, летящий в воздухе кулак, направленный на противника, принадлежит главному герою, то, как бы далеко он не находился от тела врага, травмы и ушибы у противника все равно образуются сами собой.  + + + + + + + +2. Если, летящий в воздухе кулак, принадлежит противнику и направлен на главного героя, то, как бы близко он не находился от тела героя, он не может лишить его дееспособности. Исключение: Если кулак принадлежит железной статуи, то, положение героя не отличается от положения его противников.  + + + + + + + +- Л -  + + + + + + + +Любовь  + + + +Чем сильнее мужчина и женщина ненавидят друг друга, тем больше вероятность того, что они друг друга полюбят.  + + + + + + + +- М -  + + + + + + + +Маньяк  + + + +Если вы любите каждую свободную минуту слушать оперные арии (особенно немецких композиторов) или органную музыку, или смотреть балет, изучаете экзотическую флору и фауну, знаете наизусть Библию, а еще лучше - Екклезиаст, не прочь на досуге примерить женское белье и обязательно что-то коллекционируете, вас воспитывала деспотичная мать, которая заставляла вас вышивать гладью, и, наконец, ваша первая возлюбленная изменила вам с вашим любимым хомячком или рыбками прямо у вас на глазах - значит, вы - маньяк и ваше место на экране.  + + + + + + + +Монстры  + + + +1. Какой-нибудь монстр (мутант, инопланетный гад и т.д.) который в одну секунду расправлялся с людьми, будет долго "думать" перед тем как убить героя.  + + + + + + + +2. В случае с героиней, взгляд гада будет скользить по груди и прочим выдающимся частям тела. Неужели, женские прелести ему могут быть интересны? Или все монстры - извращенцы? Это же равноценно тому, как если бы человек, прежде чем раздавить самку таракана, ласкал взглядом ее прекрасный панцирь и милые усики!  + + + + + + + +3. Любые монстры, вне зависимости от комплекции и характера, в момент гибели пищат, как маленькие девочки.  + + + + + + + +4. Обитают преимущественно в вентиляции и канализации, даже если действие происходит на космическом корабле.  + + + + + + + +5. По способам питания и способности к анабиозу ни в чём не уступают насекомым (см. Монстры-Насекомые).  + + + + + + + +6. Вид Монстры делится на Подвиды: Насекомообразные, Паукообразные, Зверообразные и Трупообразные. Нередко подвиды скрещиваются между собой и образуют достаточно сложные сочетания (например, Чужие - образованы слиянием трех подвидов: Насекомообразных, Зверообразных и Трупообразных Монстров).  + + + + + + + +Монстры, подвид Насекомообразные  + + + +1. В момент гибели делают тоже, что и все монстры (см. Монстры, п.3)  + + + + + + + +2. В местах гнездований почему-то заплетают всё паутиной и откладывают мерзкие, покрытые слизью яйца, светящиеся изнутри (как известно, реальные насекомые ничего подобного не делают).  + + + + + + + +3. Конечности оканчиваются острым штырём и запросто пробивают листовое железо.  + + + + + + + +4. Питаются исключительно людьми, если по какой-то причине людей в данной местности не наблюдается, залегают в спячку  + + + +и терпеливо ждут визита любое количество столетий.  + + + + + + + +Монстры, подвид Паукообразные  + + + +1. К своей жертве приближаются настолько неспешно, словно окружающая температура не выше -20 по Цельсию, с угрожающим рычанием (рычательный орган паука науке неизвестен) и кусают героев фильма обычно за ногу (реальные пауки прекрасно осведомлены, где у жертвы находится шея).  + + + + + + + +2. Коконы пауков непременно омерзительно пульсируют, иллюминированы так же, как и яйца насекомых (см. Монстры, подвид Насекомообразные, п. 2).  + + + + + + + +Монстры, подвид Трупообразные  + + + +1. Подвид Трупообразных монстров образован от Человека Разумного (Homo Sapiens) путем его умерщвления и последующего оживления на высокой стадии разложения.  + + + + + + + +2. Главная отличительная черта - очень медленно двигаются. С трудом волочат ноги или ползут.  + + + + + + + +3. Очень любят тянуть к жертве руки. Пальцы на руках всегда растопырены и скрючены.  + + + + + + + +4. В просторечии этот подвид называют коротким словом Зомби.  + + + + + + + +Музыка  + + + +1. В самый ответственный момент, начинает звучать музыка невидимого симфонического оркестра. Вероятно, симфонические оркестры караулят героев так же, как и поезда (см. Погоня).  + + + + + + + +2. Когда человек влюбляется, невесть откуда звучит пафосно-романтическая музыка.  + + + + + + + +3. Если с кладбища слышна мрачная заунывная музыка, то это кладбище обязательно надо исследовать. Там может оказаться что-то очень интересное, например, ожившие мертвецы или привидения.  + + + + + + + +Мэр, см. Сенатор  + + + + + + + +- Н -  + + + + + + + +Небоскреб  + + + +Если, вдруг, герой оказался на крыше высотного здания небоскреба, вместе со своим врагом, то можно быть за героя спокойным. Эффектное падение сорвавшегося с крыши высотного здания врага, обязательно завершит эту сцену.  + + + + + + + +Негры  + + + +1. Негры есть в каждом американском фильме. Как правило, негр так или иначе близок главному герою (друг, напарник, подельщик...).  + + + + + + + +Исключение: Если главный герой сам - негр.  + + + + + + + +2. Если в фильме есть только один негр - это обязательно положительный герой.  + + + + + + + +3. Если убивают одного из центральных положительных персонажей, то убить могут кого угодно, но только не негра.  + + + + + + + +Исключение: Если все герои фильма - негры.  + + + + + + + +Новый шпион  + + + +Новый шпион - экстремал, покрыт татуировками, одет в casual wear (т.е. как простой пацан). Любит рэп, хардкор и любую жесткую музыку для настоящих мужиков. Никаких признаков порочной аристократичности. Свой в доску. Во многом схож с главным Героем (см. Герой).  + + + + + + + +Примечание: Это еще не штамп, но скоро станет оным.  + + + + + + + +- О -  + + + + + + + +Одежда  + + + +1. Если герой одет в рубашку, то при драке у нее обязательно оторвутся рукава, чтоб обнажить мощные бицепсы героя. Если отрывается один рукав, то он находит время оторвать и второй. Если герой одет в майку, или футболку, то она обязательно порвется. Антигерой в основном рвет ее сам.  + + + + + + + +Исключение: герой не обладает достаточной мускулатурой, чтоб продемонстрировать ее перед камерой.  + + + + + + + +Ожог  + + + +Если отрицательный персонаж выжил в огне (или кислоте), он получает ожог именно на лице. Обожженное лицо делает его еще страшнее.  + + + + + + + +Если главный герой получает ожог на лице, он обязательно отомстит виновнику этого. Далее возможны варианты:  + + + +а) герой сделает операцию и снова станет красивым;  + + + +б) его полюбят таким, какой он есть;  + + + +в) он не станет красивым, его никто не полюбит и тогда, он отдаст всего себя на служение во благо человечеству. Этот вариант предполагает продолжение.  + + + + + + + +Очки  + + + +Если, в начале фильма главный герой носит очки, то, очень скоро, он эффектно избавляется от них, символизируя этим поступком обретение уверенности в своих силах и готовность к новым свершениям. Таким образом, очки главного героя никогда не доживают до финала.  + + + + + + + +Исключение: См. Солнцезащитные очки  + + + + + + + +Оружие  + + + +Негодяи и бандиты очень часто вооружены советским оружием. Как правило, это - АК.  + + + + + + + +См. также Пистолет, Бомба.  + + + + + + + +Охрана  + + + +1. Охрана секретных баз (варианты: лабораторий, пусковых шахт баллистических ракет) - работа крайне низкооплачиваемая и далеко не престижная. Берут на нее исключительно слепоглухонемых кретинов с врожденным церебральным параличом. Дабы эти страдальцы не застрелили кого-нибудь с перепугу - их вооружают только оружием с холостыми патронами.  + + + + + + + +2. Охранник антигероя - это всегда огромный жирный негр, не способный даже поднять ногу выше колена.  + + + + + + + +3. Чтоб попасть на любой охраняемый объект достаточно дать охраннику от 20 до 100 баксов.  + + + + + + + +- П -  + + + + + + + +Парад  + + + +Спасаясь от погони, всегда можно спрятаться в толпе проходящего мимо парада. Какой-нибудь парад обязательно будет.  + + + + + + + +Пистолет  + + + +1. На дистанции до километра, обычный пистолет значительно превосходит и автоматическое оружие, и даже ядерное как по убойной силе, так и по количеству патронов.  + + + + + + + +2. Именно пистолет помогает осуществить миссию по спасению мира.  + + + + + + + +3. Шестизарядные пистолеты стреляют по 30-40 раз (видимо, их умудряются заряжать магазинами от автомата).  + + + + + + + +Плохой  + + + +1. У плохого человека, который на протяжении фильма совершает плохие поступки, есть только два пути - погибнуть или исправиться.  + + + + + + + +2. Если плохой человек исправляется, он все равно погибнет ближе к концу фильма. Причем, погибает он сразу, как только совершил геройский поступок. Таким образом, у плохого человека нет никаких шансов выжить.  + + + + + + + +Погоня  + + + +1.В сцене погони на машинах, на пути героя или его преследователей обязательно находится лоток с фруктами, или кто-то решил выгрузить на тротуар картонные коробки, или бидоны с молоком, или посуду. Пешеходы и просто люди, идущие по краю тротуара или переходящие улицу, во время неожиданного появления из-за угла машин со скоростью не меньше 300км/ч, всегда успевают увернуться и отпрыгнуть в сторону.  + + + + + + + +2.В сцене погони на машинах, на пути героя или его преследователей непременно оказывается железнодорожный переезд с обязательным поездом, у которого, не меньше 50-ти вагонов. При этом, герой всегда успевает проскочить перед самым носом паровоза и попасть за переезд, а его преследователи остаются на другой стороне, такой маневр позволяет герою скрыться или получить преимущество как минимум в час. Видимо, поезда, только и делают, что караулят главных героев и, завидев их, сразу спешат на помощь.  + + + + + + + +3.В сцене погони, перед преследующими героя полицейскими на машинах, обязательно оказывается огромный грузовик, переезжающий улицу или совершающий разворот. Грузовик обычно караулит именно полицейских.  + + + + + + + +4.В случае начавшейся погони, любые полицейские машины, оказываются позади преследуемых, даже если они преследуют колонну трехколесных велосипедов, и создают умильную суету.  + + + + + + + +5. При погоне на машинах самой легкой мишенью для стрельбы оказывается:  + + + +- для преследующей машины - зеркальце заднего вида,  + + + +- для преследуемой - оружие или рука с оружием, торчащая из окна и стреляющая по машине.  + + + + + + + +6. Если в преследующей машине сидят положительные персонажи, то тот, кто сидит рядом с водителем обычно не умеет ни стрелять, ни водить машину, и они на ходу начинают меняться местами. Если пассажир все же берет пистолет, то после первого же выстрела или встряски он его роняет.  + + + + + + + +Поцелуй  + + + +2. Когда главный герой с героиней целуются стоя, то героиня обязательно сгибает одну ножку в колене. В обычной жизни ни одна женщина так не поступает. Видимо у женщин на экране подразумевается наличие особого сухожилия, связывающего одну из ног и язык: как только язык выдвигается для глубокого поцелуя, сухожилие натягивается и невольно поднимается нога.  + + + + + + + +3. Если в фильме попадается особо романтическая сцена поцелуя, то она всегда происходит на фоне горящего камина.  + + + + + + + +4. При поцелуе, у героини никогда не размазывается помада (неужели, все, о чем говорится в рекламе Max Factor - правда?)  + + + + + + + +Исключение: помада размазывается у той женщины, которая не разборчива в половых связях (неужели распутные женщины ничего не знают про Max Factor?)  + + + + + + + +5. После 15 лет совместной жизни любящие супруги продолжают каждые пять минут страстно целоваться взасос.  + + + + + + + +Продукты  + + + +1. Продукты носятся в неудобных бумажных пакетах без ручек и таят потенциальную опасность рассыпаться. Нередко рассыпаются, причем особенно часто рассыпают свои покупки матери-одиночки. Это нужно, чтобы матери-одиночки выглядели особенно жалобно.  + + + + + + + +2. Во всех пакетах с покупками ОБЯЗАТЕЛЬНО есть хотя бы один длинный батон, причем эти батоны никогда не едят.  + + + + + + + +3. Продукты часто рассыпаются, когда Антигерой (Убийца, Маньяк и т.д.) должен подойти к будущей Жертве сзади. Видимо, продукты все-таки находятся на стороне Зла - в отличие от Мостов, Поездов, Шлагбаумов, Струй пара и Неустойчивых конструкций (см. соответствующие разделы), которые всегда стоят службе у Добра.  + + + + + + + +Полицейские  + + + +1.Полицейские, проводя любого рода расследования, обязательно хотя бы раз зайдут в стрип-клуб. Это необходимо для того, чтобы в фильме всегда была определенная доза женского тела.  + + + + + + + +2.Прокурор никогда не выдает ордер на обыск квартиры самого злостного преступника, поэтому, полицейскому детективу приходится проводить его без санкции.  + + + + + + + +3. Почти все полицейские одиночки выполняют задания на личных машинах и обязательно их разбивают, причем, начальники отказываются выдать им новые, или возместить ущерб.  + + + + + + + +Исключение см. п. 4  + + + + + + + +4. Личные машины полицейских одиночек в 50% это или Ferrari, или porche за $100.000, которые они покупают на свою честную зарплату. Что бы не делали с такими машинами, они всегда остаются целыми.  + + + + + + + +5. Полицейские-напарники обязательно разных национальностей. Чаще один из них белый, а другой черный (хотя, это не столько штамп, сколько суровая необходимость, иначе режиссёра могут обвинить в расизме и затаскать по судам).  + + + + + + + +6. Почти все полицейские предпочитают работать в одиночку и очень не любят когда им предлагают напарников, с которыми они сначала не могут поладить, а потом, к концу фильма, становятся друзьями.  + + + + + + + +7. Если полицейскому скоро на пенсию, его обязательно убьют.  + + + + + + + +8. Если полицейским требуется остановить преступника, убегающего на машине, им приходится разбить примерно пятьдесят полицейских машин, раздолбать и покорежить примерно двести автомобилей посторонних лиц, заставить разбежаться в разные стороны еще пятьсот случайных прохожих, однако преступник при этом все равно убегает. Но его остановит герой. В одиночку. Идя пешком.  + + + + + + + +9. Полицейские участки тщательно тестируют своих офицеров, чтобы убедиться, что те выбирают себе напарников, являющихся их полной противоположностью.  + + + + + + + +10. Вдовы убиенных полицейских получают, как правило, несколько миллионов зелени, в качестве моральной компенсации.  + + + + + + + +11. Когда в фильме что-нибудь случается (даже если котенок залез на дерево и не может слезть обратно) на место происшествия приезжает от 15 до 50 полицейских машин с включенными сиренами, причем немедленно.  + + + + + + + +Исключение: Финальный бой Героя и Антигероя. Полицейские машины ожидают победы Героя вне зоны видимости и появляются исключительно "по факту".  + + + + + + + +Пончики  + + + +Пончики - это не просто продукт питания, это профессиональная пища полицейских, так как поедание их сопряжено с опасностью и риском. Пончики являются главным искушением для полицейского, бороться с которым невозможно, они же зачастую и провоцируют преступника на решительные действия. Правило гласит, что полицейский может есть пончики только в машине, и исключительно во время наблюдения за подозрительным объектом. Когда пончики едят несколько полицейских, все под контролем, но, стоит кому-то одному отправиться за порцией горячих пончиков - беды не миновать.  + + + + + + + +Пот  + + + +1. Пот героя - жирный и блестящий. Место его скапливания - лоб, спина и подмышки. Место скапливания пота антигероя - все остальное.  + + + + + + + +2. Героиня никогда не потеет, чтобы на ней не было одето или сразу потеет с ног до головы, если находится в постели с мужчиной своей жизни.  + + + + + + + +Исключение: если героиня - солдат Джейн.  + + + + + + + +Пули  + + + +1. Обычные пули очень любят светиться в полете и приносить своим жертвам ужасные рваные ранения.  + + + + + + + +2. Ношение на шее или в нагрудных карманах амулетов (бус, талисманов, значков, медалей, портсигаров, партбилетов) делает человека неуязвимым для пуль. Видимо, пули всегда чувствуют эти предметы и направляются именно к ним.  + + + + + + + +- Р -  + + + + + + + +Работа  + + + +Работа заставляет отца забывать о том, что у его сына сегодня восьмой день рождения.  + + + + + + + +Разговор  + + + +Если герой обнаружил, что его неправильно поняли, и это недопонимание можно исправить простым объяснением, то он обязательно промолчит, чтобы навлечь на себя массу проблем.  + + + + + + + +Ранение  + + + +1. После самого тяжелого ранения (в том числе выстрела в сердце в упор), положительный герой никогда не умирает сразу. Он обязательно попрощается с близкими, раскроет пару тайн и изобличит врага. Только тогда он умрет со спокойной совестью. То же происходит со смертельно ранеными негодяями, которым нужно успеть исправиться перед смертью.  + + + + + + + +Исключение: контрольный выстрел в голову  + + + + + + + +2. Входное отвертие от обычной пули больше напоминает выходное от разрывной.  + + + + + + + +Исключение: фильмы Спилберга про войну, советские фильмы, романтические мелодрамы.  + + + + + + + +3. Мужчина не обращает внимания на боль даже при самом жутком избиении, но вздрагивает, когда женщина промывает ему раны.  + + + + + + + +Реплики  + + + +В остросюжетных сценах положительные герои любят задавать друг другу один вопрос: "Ты в порядке?" Причем, у героя может быть сломана нога или рука. Этой фразой герои как бы говорят нам, зрителям: "Мы очень хорошие и добрые! Видите, как мы заботимся друг о друге?"  + + + + + + + +Роботы  + + + +Когда в фантастических фильмах хотят показать как робот видит действительность, т.е. взгляд от первого лица, - всегда используется один и тот же штамп: на экране (который расположен наверное внутри глаза) бегут цифры, происходят какие-то расчеты, двигаются геометрические фигуры. Делается это конечно для того, чтобы зрителям было видно как он "думает".  + + + + + + + +Русские  + + + +1. Лица русской национальности бывают двух видов:  + + + +а) русская мафия  + + + +б) русские шпионы  + + + + + + + +Исключение: Русские фильмы  + + + + + + + +Русские независимо от пола, возраста, местонахождения и положения в обществе носят ушанку, пьют водку из самовара и закусывают чёрной икрой.  + + + + + + + +Исключение: угрюмые и злобные агенты КГБ, то есть примерно 50% населения России, судя по их количеству в Америке.  + + + + + + + +- С -  + + + + + + + +Самолет  + + + +Посадить самолет может каждый, это умение заложено в генах любого человека. Также, каждый может посадить вертолет, впрочем, поднять его в воздух тоже каждый может.  + + + + + + + +Сенатор  + + + +Если в фильме есть сенатор (или мэр), то как правило, он куплен мафией. В конце фильма (и не раньше) его разоблачают. Тогда он кончает жизнь самоубийством - из патриотических и политкорректных соображений госдеятелей не убивают, они сами уходят из жизни, терзаемые совестью.  + + + + + + + +Слизь  + + + +Человек на 90% состоит из воды, инопланетянин или любой другой монстр на 90% состоит из слизи. Слизь всегда обладает повышенной тягучестью.  + + + + + + + +Сон  + + + +Любой человек, проснувшийся от кошмарного сна, резко садится и начинает тяжело дышать.  + + + + + + + +Солнцезащитные Очки  + + + +Всегда используются не по назначению:  + + + + + + + +1. Символизируют особую крутизну героя (или антигероя). Носятся при этом также и в темное время суток, и даже в полной темноте.  + + + + + + + +2. Служат для маскировки лица. Принято считать, что стоит спрятать глаза за темными очками, как тебя не узнает никто, даже ближайшие родственники.  + + + + + + + +Стекла  + + + +При стрельбе по стеклянным дверям пуля падает сразу за дверью. Иначе как объяснить, что следа от пули не остается на противоположной от двери стене или вторая стеклянная дверь всегда остается целой.  + + + + + + + +Исключение: За дверью стоит человек, в которого стреляют.  + + + + + + + +Схватка  + + + +1. Если у одного из дерущихся в руках палка или меч, нечто подобное в обстановке находится и для второго (торшер, карниз, клюшка для гольфа, кочерга, копье...)  + + + + + + + +2. Если антигерой в процессе боя теряет меч, то благородный герой его возвращает, чтобы продолжить поединок в равных условиях. Видимо, чтобы стать Героем, необходимо быть немного мазохистом.  + + + + + + + +См. также Финальная схватка  + + + + + + + +- Т -  + + + + + + + +Темнота  + + + +Темнота в кино бывает полной крайне редко. Обычно, после выключения света в комнате, вполне можно читать.  + + + + + + + +Телефон  + + + +1. Все телефоны в Америке начинаются с 555  + + + + + + + +Примечание: Вообще-то это не столько штамп, сколько правило, принятое в обращение всеми американскими киностудиями. В фильмах используется один и тот же несуществующий номер телефона. Это делается с целью не упоминать реальные номера и, таким образом, уберечь реальных людей от непрошенных звонков (несколько лет назад, с этим был даже связан небольшой скандал - Квентин Тарантино в фильме <Джеки Браун> проигнорировал это правило и владелец номера, упомянутого в фильме, подал на Тарантино в суд). Правило распространяется также на номера машин, адреса и т.д.  + + + + + + + +2. Чтоб набрать любой номер, достаточно нажать две кнопки.  + + + + + + + +Исключение: 911, любой экстренный случай, когда номер нужно набрать быстро. В этом случае, для набора номера кнопки придется нажимать раз двадцать.  + + + + + + + +3. Если на улице звонит телефон-автомат, и вокруг него полно народа, трубку снимает тот, кому предназначается звонок, даже если он до этого момента вообще не ждал звонка.  + + + + + + + +4. Для локализации входящего звонка всегда требуется 30 секунд. Если по сюжету этого времени не хватает, то время продлевается до минуты. Говорящий всегда укладывается в это время, даже не глядя на часы. Если все-таки удается локализовать звонок, то на месте говорящего оказывается диктофон.  + + + + + + + +5. Если герою требуется срочно позвонить из телефона-автомата, то у телефона оказывается огромная очередь, а все остальные телефоны в округе не работают.  + + + + + + + +6. Если в телефонной будке есть справочник, то из него обязательно вырвут страницу с телефоном.  + + + + + + + +Трейлер к фильму  + + + +В Трейлер включаются самые интересные и откровенные моменты фильма. Нередко, посмотрев Трейлер, фильм можно не смотреть.  + + + + + + + +Туалеты  + + + +1. У героев не бывает никаких проблем с туалетами. Просто у ВСЕХ героев фильма другая физиология. Даже у связанных заложников.  + + + + + + + +Исключение: посещение туалета необходимо для побега.  + + + + + + + +2. Туалет служит для того, чтобы герои фильма могли обсудить свои дела: мужчины - мужские, женщины - женские.  + + + + + + + +3. Если герои фильма в каком-либо учреждении (банк, полиция и т.д.), они очень любят обсуждать вопросы именно в мужском туалете.  + + + + + + + +4. Туалет - отличное место для приема наркотиков, переодевания и кровавой разборки.  + + + + + + + +5. Часто в мужском туалете встречается один и тот же персонаж: лысеющий мужчина средних лет в очках, который обязательно сидит на унитазе и, так или иначе, вынужден страдать от действий героев: ему выбивают дверь, дергают его за ноги, которые видны в проеме под дверью, а чаще всего, ему просто вдребезги разносят кабинку.  + + + + + + + +- Ф -  + + + + + + + +Фильм  + + + +Штампам подвержено не только содержание фильма, но и сам фильм, как таковой существует по определенному шаблону:  + + + + + + + +1. Если фильм получился более или менее кассовым, то сразу же начинается сниматься продолжение (сиквел).  + + + + + + + +2. Если сиквел не имеет коммерческого успеха - снимается приквел. Если же и он не имеет успеха, режиссёр ждёт десяток лет и снова снимает этот же фильм с новыми актёрами (римейк).  + + + + + + + +3. На особо удачный фильм сразу же начинается сниматься сериал или мультсериал.  + + + + + + + +4. Раньше (лет 10 назад) сиквелы всегда уступали оригиналу. В последние годы сиквелы стали лучше и, как правило, превосходят оригинал. Но это тема отдельной статьи.  + + + +см. также Трейлер к фильму.  + + + +Финальная схватка  + + + +1. В финальной схватке у героя и антигероя патроны заканчиваются одновременно, и они сражаются голыми руками.  + + + +Исключение: Патроны заканчиваются только у одного. Но, при этом, второй все равно бросает оружие и принимает бой врукопашную из благородной солидарности, или "на слабо", в зависимости от того, герой это или антигерой.  + + + +2. Очень часто Антигерой, до этого убивавший налево и направо людей из пистолетов и пулеметов и ультрасовременных оружий - при виде Героя сразу обретает совесть и выходит на схватку с Героем на кулаках. Максимум - на ножах, финках и т.п.  + + + +3. В начале финальной схватки Герой всегда уступает Антигерою, но исход боя нередко решает именно Героиня. Услышав писк привязанной к бомбе (см. Бомба) Героини, Герой воодушевляется и мощным апперкотом отправляет Антигероя в полный нокаут или отлетая, Антигерой падает, например, в лаву (во второй части фильма за него вполне может отомстить брат Антигероя, или Антигерой незаметно выплывет из лавы отделавшись ожогом на пол- лица. См. Ожог  + + + +4. Финальный бой, как правило, происходит на заброшенной фабрике или заводе. См. Заброшенная фабрика или завод.  + + + +5. Когда Антигероя убили, а Герой и Героиня летят навстречу друг другу в объятья, убитый Антигерой вдруг оживает, чтоб получить пулю или автоматную очередь от:  + + + +- Героя;  + + + +- Героини;  + + + +- Второстепенного героя;  + + + +- Второстепенного Исправившегося Антигероя.  + + + +6. Если схватка происходит на крыше движущегося вагона (фуры, автобуса), и Антигерой, разоружив Героя, собирается его прикончить, на помощь всегда приходит мост или туннель. Герой всегда знает, когда нужно пригнуться и дать таким образом Антигерою быть сбитым и поверженным. Мосты и туннели всегда встают на пути у зла и равноценны по своей функции Героине, Другу Героя (см. п.5) или поезду (см. Поезд).  + + + +7. Антигерой всегда испытывает проблемы с логикой и ему тяжело дается понимание причинно-следственных связей. Например, он всегда с готов стрелять во врагов, если они стоят перед главной опорой тяжелой, опасной и неустойчивой конструкции, которая, падая, уничтожит его самого.  + + + +8. После финальной схватки, закончившейся победой Героя, издалека обязательно доносится вой сирен полицейских машин. Видимо, полицейские все это время терпеливо ждут, пока Герой сделает свое светлое дело.  + + + +- Х -  + + + +Холодильник  + + + +1.Обычно, в кухнях не бывает освещения. Если герою нужен ночью свет на кухне, он просто открывает дверь холодильника, откуда и излучается необходимый свет. А еще в холодильнике всегда есть баночка пива.  + + + +2. Если холодильник всегда пустой и в нем нет ничего, кроме обязательной баночки пива (см. пункт 1) и пакета протухшего молока, это совсем не означает, что хозяин холодильника сидит на диете или уже умер от истощения, это просто значит, что холодильник стоит в квартире героя, у которого ушла жена, либо герой - полицейский.  + + + +- Ш -  + + + +Шайка антигероя  + + + +1. Шайка Антигероя живет только для того, чтобы умереть быстро и красиво. Для того, чтобы Герой мог попасть к Антигерою, он должен преодолеть огромную кучу "сподвижников" Антигероя. Сподвижники гибнут пачками под кованными лаптями Героя и Геройским пистолетом. Герой, продвигаясь сквозь ряды наемников Антигероя подвергается шквальному огню, в котором максимум - ему могут случайно поцарапать костюм, пролетевшей рядом ракетой от базуки. Герой же, в свою очередь попадает в наемников с первой попытки, с разворота, в сальто, из под ноги, с закрытыми глазами и наугад. Причем пользуется он пистолетом с магазином в 100-150 патронов (см. Пистолет).  + + + +2. Если в вас стреляют враги, то нырните в реку или хотя бы ванну - вражьи пули никогда не пробивают воду.  + + + +3. Владельцы частных вертолетов всегда охотно нанимаются на работу к террористам, даже если эта работа состоит в отстреливании всех незнакомцев и заканчивается огненным шаром, в который превращается этот вертолет.  + + + +Шеф  + + + +Шеф полиции постоянно отстраняет лучшего детектива от работы или дает ему не больше 48 часов, чтобы закончить дело.  + + + +Шпион  + + + +1. Чем отличается настоящий шпион от рядового опера? Настоящий шпион никогда не опустится до того, чтобы скрывать свою личность и никогда не будет напрягаться, чтобы его не узнали. Он просто выше всего этого.  + + + +2. Когда в каком-нибудь мексиканском, африканском, пуэрто-риканском, багамском или еще каком-нибудь захолустном поселке появляется супершпион, это видно сразу: костюм от Хьюго Босс, швейцарские часы Ротари, эксклюзивная тачка и запоминающяяся прическа или лицо. Вот визитная карточка настоящего шпиона.  + + + +3. Является главным Героем, но по многим характеристикам напоминает Антигероя, хотя и спасает мир (см. Герой, Антигерой). )  + + + +Исключение: Вин Дизель - новый шпион, который клал все свои иксы на всех старых шпионов.  + + + +3. Имя и фамилия шпиона в сумме не должно превышать лимит в 10 букв, больше не запомнят.  + + + +- Э -  + +Эйфелева Башня  + + + +Эйфелева башня в Париже видна из КАЖДОГО окна.  + + + +- Я -  + + + +Яд  + + + +Яд, подсыпанный в бокал, никогда не поражает намеченную жертву. Так или иначе, но Яд всегда получает тот, кто его подсыпал.  + + + +Яды и вирусы  + + + +Яды и вирусы (смертльные и / или вызывающие мутации) могут находится в теле несколько дней, но при этом, чаще всего, известно точное время с точностью до минуты, когда они сработают. Причем, антидот или лекарство, введенное за мгновение до времени Ч, срабатывает сразу.  + + + +Японцы  + + + +Японцы в кино бывают трех видов:  + + + +1. Перманентно улыбающиеся туристы с фотоаппаратами. Полные идиоты, иногда гибнут всей стаей.  + + + +2. Бизнесмены, собирающиеся подписать некий многомиллионный контракт. Как правило, контракт подписать так и не удается. diff --git a/personal/creative/humor/Кросавчег.md b/personal/creative/humor/Кросавчег.md new file mode 100644 index 00000000..6a1417f2 --- /dev/null +++ b/personal/creative/humor/Кросавчег.md @@ -0,0 +1,452 @@ + +Медведь, переминаясь с лапы на лапу, тревожно всматривался в кедровый бор у подножия сопки. Время от времени он дышал на стекло стереотрубы, подзывал зайца и вытирал ии объектив. Стереотрубу Мише подарили геологи, как и карабин, теодолит и кучу другого бесполезного в хозяйстве барахла. Той весной Топтыгин поднялся поздно, и, продирая глаза обходил свои владения, когда внезапно наткнулся на лагерь.  + + + +При виде медведя люди в лагере обрадовались, начали кричать, бегать, но когда тот подошел поближе, засмущались и быстро уплыли на лодке. Миша и сам был зверь застенчивый, поэтому просто перетаскал подарки в берлогу и долго разбирался, что тут к чему. Большинство приборов так и остались непонятными игрушками, но трубу Топтыгин полюбил и, прихватив с собой зайца, часто бродил по сопкам, любуясь видами. + + + +Наконец между между стволами замелькало серое, и Хозяин Тайги облегченно вздохнул: до последнего момента он не был уверен, что Волка отпустят с заставы живым. + + + +- Он что, всю дорогу бежал, что ли? - мяукнула Рысь. + + + +Гигантская кошка обладала удивительно острым зрением и без всякой трубы видела ходящие ходуном бока соседушки. Багровый язык, свешенный чуть не до земли, говорил о том, что Волк действительно преодолел все двадцать километров без остановки. Наконец, Серый забрался на сопку и свалился прямо у ног Медведя. + + + +- Все, больше не могу, - прохрипел он, - Дайте отдышаться. + + + +И немедленно заснул. Медведь подождал пять минут, затем взял Волка за шиворот и слегка встряхнул. + + + +- Сейчас-сейчас, еще минуточку, - пробормотал Волк, не открывая глаз. + +- Серый, хватит придуриваться, - рявкнул Медведь. + +- Все-все, уже проснулся, - Волк открыл глаза и протер их лапами. + +- Так что тебе сказали? - спросил Топтыгин. + +- Э-э-э, а можно меня на землю поставить? - робко поинтересовался Серый. + +- А. Извини. + + + +Волк встряхнулся и обвел собравшихся нехорошим взглядом: + + + +- В общем, информация подтвердилась, - сказал он, - Целая бригада браконьеров, работает по заказу китайцев. + +- И что им нужно? - нервно спросил Медведь. + +- Ну... - помялся Волк, - Лапы медведя, уши рыси и тигриные... + + + +Он приподнялся на задние лапы и что-то прошептал в ухо Тигру. Полосатый, обычно оранжевый, вдруг пожелтел со шкуры и очень плотно припал к земле. + + + +- Яйца, значит, - вздохнул Медведь, - А что пограничники? Не помогут в этот раз? + +- У них и так забот полон рот, - мрачно ответил Волк, - Косоглазые через Амур лезут - спасу нет, еле успевают отлавливать. + +- Я бы попросил! - возмутился Заяц. + +- Извини. Берта вот согласилась с нами поработать, у нее отпуск. + +- Это твоя знакомая? - уточнил Медведь. + +- Ну, почти, - Волк посмотрел в небо, - У нас через полтора месяца щенки будут. В порядке эксперимента разрешили. + +- Понятно, - Топтигин осмотрел собравшихся, - Ну, стало быть, обороняться будем сами. + +- Милиция велела передать, - добавил Серый, - Что искать их, если что не будут. + +- Поня-я-ятненько, - мяукнула Рысь. + +- Ладно, Заяц, можешь быть свободен, тебя это не касается, - приказал Хозяин Тайги. + +- Фигу! Это мой лес! - храбро ответил Косой, - Сегодня вас, завтра им мои глаза потребуются или еще что-нибудь. + +- Молодец, - Медведь осторожно потрепал Зайца по ушам, - Значит, план у нас будет такой... + + + +***** + + + +Волк и Берта неспешно трусили по тайге, уткнувшись носами в свежий след. браконьеров было семеро, и эти идиоты не нашли ничего лучше, чем, разделиться. Медведь взял на себя троих, за двоих отвечал тигр, Рыси и Волку с Бертой досталось по одному. Теперь нужно было только настигнуть своего мерзавца и наброситься на него сзади. Они гнали браконьера уже полтора часа, подбадривая его бодрым воем. + + + +- Городской, - удовлетворенно заметил Волк, - Местный бы костер развел и за ним отсиделся. + +- Не забывай, - нервно заметила овчарка, - У него карабин. Один раз бахнет - будешь потом свинец переваривать. + + + +За время службы на границе Берта была ранена дважды и в таких вопросах считалась экспертом. + + + +- Да брось, - хвастливо ответил Серый, - Он уже от страха не видит, куда бежит, еще немного... + +- Стой, - овчарка замерла, и Волк, не успевший остановиться, проехал вперед на заднице несколько шагов. + +- Что такое? - удивленно обернулся Волк + +- Запах сильный, кажется он... + + + +Над кустами медленно поднималось дуло винтовки. + + + +- Берегись! + + + +Берта рванулась вперед, но Серый сшиб ее плечом и кинулся в кусты сам. Раздался выстрел, короткий вой. Не помня себя от горя и ярости, овчарка бросилась в атаку, целясь туда, куда положено целиться служебной собаке... + + + +***** + + + +Заяц нашел Тигра посреди широкой старой гари. Могучий кот внимательно рассматривал широкую полосу распаханной и развороченной земле. Время от времени он опускал нос вниз, пытаясь принюхаться, но, вдохнув пыль и старую золу, чихал и опять усаживался на задницу. Заяц осторожно приблизился к Полосатому. Вообще-то, тигры не удостаивали косых своим вниманием, предпочитая зверей покрупнее, но кто его знает. Ополчение ополчением, а вдруг ему жрать захочется? + + + +- Гумус, - задумчиво произнес Тигр, цепляя лапой землю. + +- Что? - вежливо спросил заяц. + +- Почва, плодородный слой, - все так же величественно ответил могучий зверь, - Все мы возвращаемся в гумус, давая жизнь травам, от трав кормятся благородные олени и прочие копытные, а они питают нас, мы же, когда придет срок, вернемся сюда, в гумус. + + + +Заяц тактично помолчал. Тигров нередко пробивало на философию, Волк как-то рассказывал, что его зимой поймала одна Тигрица, и вместо того, чтобы разорвать, три часа читала выдержки из "Лунь Юй" и "Парадоксов". + + + +- Понятно, - наконец нарушил молчание Косой, - А где браконьер? + +- Ну... - пожал плечами Тигр, - где-то тут должен быть. Я на него стадо кабанов выгнал... + + + +Полосатый встал и прошелся вдоль перепаханной полосы. + + + +- В общем, похоже, он вернулся в гумус. Ладно, пойду второго искать. + +- А что его искать? - Заяц, ждавший этого момента, надулся от гордости, - Пойдем, он тут, недалеко валяется. + +- В каком смысле? - переспросил Тигр. + +- Завалил я твоего второго, - чуть лопаясь от сознания собственного величия ответил Заяц. + +- Хм-м-м, интересно, - пробормотал Тигр, - Ну, показывай. + + + +Браконьер лежал в кустах без движения с закаченными глазами. + + + +- Нда, действительно, - протянул Тигр, - Ладно, рассказывай, как ты его? + +- На самом деле очень просто, - заторопился Заяц, - он идет такой, идет, а я прямо перед ним из травы как подпрыгну! Он брык - и лежит. + +- Забавно, - позволил себе улыбнуться Тигр, - Причем, судя по запаху, еще и обделался. Ладно, оттащи куда-нибудь ружье, а я постараюсь преодолеть природную брезгливость и отнесу засранца на сборный пункт. + + + +Заяц с трудом сдвинул винтовку с места и подумал, что, похоже, придется звать на помощь родственников. Полосатый, уже закинувший бесчувственного браконера себе на спину, внезапно обернулся. + + + +- Заяц? + +- Да? - Косой, пыхтя, пытался тянуть ружье за ремень. + +- Ты молодец. Спасибо! + + + + + +***** + + + + + +- Ну ладно, ну перестань, - успокаивала Берта Волка. + +- Что значит перестань? - визжал Серый, - Как это перестань? Думаешь я не видел, куда ты его кусала? + +- Я очень испугалась, что он тебя застрелил, и сделала то, что нас учат делать, - объяснила овчарка. + +- Вот так вот и положено? - кипятился Волк, - Думаешь мне приятно, что моя женщина хватала пастью за... + +- Ну хватит! - гавкнула Берта, - Все овчарки хватают именно за это! Нас так готовят! + + + +Волк несколько притормозил и недовечиво покосился на подругу. + + + +- Все? И кобели тоже? + +- И кобели, - твердо ответила овчарка. + + + +Некоторое время оба бежали молча. + + + +- Тяжелая у вас работа, - вздохнул Волк. + + + +Дикий, протяжый вопль разорвал тишину ночного леса. Вопль столь жуткий, что Берта прижала уши к затылку, припала на живот и тихо заскулила. + + + +- Эт-то что было? - заикаясь спросила отважная овчарка. + +- Это? - шерсть на загривке у Волка встала дыбом, - Я тебе скажу что это было. Это рыси трахаются, вот что это было. Ну, скотина с кисточками, мы тут жизнью рискуем, а она... Можно подумать, это наши уши им нужны! Так, пойдем разбираться. + + + +Припадая на раненую ногу, Волк похромал туда, где в ночи раздавались душераздирающие вопли. Взобравишись на сопку, Серый заорал: + + + +- Рыся, ты где? Вылезай, скотина, опять валерьянки обожралась? А кто за тебя браконьеров ловить будет? + +- Ну чего ты орешь, - ответило из кустов мягким, рокочущим баритоном. + + + +В темноте зажглись две пары зеленых глаз, и Волк попятился. Ирис, супруг Рыси, считался мужчиной серьезным, ходили слухи, что с ним даже Топтыгин иногда здоровается первым. Однако праведный гнев, помноженный на боль в поврежденной лапе, взяли верх, и Серый грозно уткнулся носом в широкую хитрую рожу с бакенбарадми. + + + +- В то врремя как весь лес в едином поррыве боррется с угррозой, - порыкивая начал Волк, - кое кто... + +- Да ладно тебе, Волчик, - Рыся, потягиваясь, вышла из кустов и встала рядом с мужем. + +- Привет, Берта. Мы своего браконьера обезвредили. + +- Да? Ну и где он? - кипятился Волк. + +- А вон, на дереве. + + + +Ирис подошел к здоровенном кедру с абсолютно гладким стволом, и, встав на задние лапы, с душераздирающим скрежетом поточил о кору огромные когти. + + + +- Муарррроуумуамуа, - произнес Рысь, тщательно выговаривая каждую букву. + + + +Откуда-то из кроны донесся задушенный всхлип. + + + +- Мы, собственно, его выследили, - объясняла Рыся Берте, - А когда он сел под деревом, занялись любовью. Он даже сапоги скинул, не говоря уж о винтовке. + +- Пусть посидит пока, - лениво заметил Ирис, - Утром сам свалится. А ружье я пойду в болоте утоплю. + + + + + +***** + + + + + +Медведь сменил позицию и осторожно выглянул из-за камня. Пули цвикнули в полуметре от рыла и он откатился назад. + + + +- Ну, скоро вы там? - прорычал Топтыгин. + +- Сейчас все будет, Миша, - торопливо ответи енот, загоняя маленькими лапками последний патрон в обойму. + + + +Огромные когти не давали Хозяину тайги перезаряжать карабин самому, пришлось привлечь старого знакомого. Енот пару зим пользовался топтигинской берлогой и теперь пришло время вернуть должок. + + + +- На, держи. + + + +Медведь подхватил карабин и, просунув коготь в спусковую скобу, выпалил в сторону кустов. В ответ прогремело несколько выстрелов, правое ухо обожгло болью. + + + +- Миша, - осторожно сказал енот, - А у тебя в ухе дырка. + +- Ничего, - сквозь клыки ответил Топтыгин, выпуская еще несколько пуль в сторону противника. + +- Патронов у тебя еще на одну обойму, - добавил енот. + + + +Медведь потер кровоточащее ухо лапой и осторожно высунулся из-за камня. Пуля ударила рядом, и осколки гранита больно посекли нос. Пора было менять позицию. Перестрелка продолжалась уже час, за это время медведь успел расстрелять большую часть оставшихся от геолгов патронов, но хуже всего было то, что вражеские пули разбили любимую стереотрубу. Вообще говоря, теперь все зависело от того, сработает ли вторая часть его гениального плана. Патроны кончались, горизонт уже засветлел, еще немного - и взойдет солнце. Топтыгин вздохнул, переполз за куст и открыл огонь. Он стрелял за свои лапы, за лапы всех медведей и медведиц, за уши рысей, за яйца тигров, за свой лес и свой Амур. Чувство собственного героизма и отважной обреченности переполнило косолапого и он громко заревел, бросая вызов браконьерам. Снова загрохотали выстрелы, вокруг падали срубленные пулями ветви. + + + +- Врешь, не возмешь, - прохрипел Топтыгин. + + + +- Туруру! Ла-ла-ла! Какой чудесный день! Какой чудесны пень! Кто лось - тот не олень! + + + +Басовитый, трубящий рев раздался над лесом, сопровождаемый сучьев, ударами по чему-то мягкому и сдавленными воплями. Медведь обессиленно привалился к камню - помощь подошла вовремя. + + + +- Это что? - осторожно спросил енот, прислушиваясь к звукам снизу, где, судя по всему, кого-то топтали ногами. + +- Это лоси, - расслабленно ответил косолапый, - Я этих уродов отвлекал, а они бесшумно с тыла зашли. + + + +Кому-то прописали крепкую саечку и тот же бас продолжил на мотив румынско группы, которую постоянно крутили на заставе: + + + +- Хо-хо-хо! Как по лесу пробежал олень, озорной олень, зажигательный олень! Зацепился на бегу за пень! + +- А... - енот собрался с духом и продолжил, - А чего это они? + +- Понимаешь, - медведь прикладывал к простреленному уху подорожник, - У них сейчас гон. Поэтому они гонят. Постоянно. Не обращай внимания. + + + +Из кустов вылетел огромный лось, недавно сбросивший рога. + + + +- Ха-ха-ха! Мой добрый друг медведь! Ого-го-го! и маленький зверек с полосатой мордой! + +- Енот, извините, - застенчиво представился Енот. + +- Енот, уху-ху, какой великолепное, величественное, сексуально привлекательное имя! Не обращайте внимание, я гоню. + +- Сексуально привлекательное? - робко спросил зверек у Косолапого. + +- Не обращай внимания, он гонит, - заверил Медведь, - Нормальное имя, мужское такое, крепкое. Лось, спасибо, уж выручил так выручил. + +- Да-да-да! Мы мужественные звери, мы должны помогать друг другу! Тем более, что им нужны были наши хвосты. У-дю-дю-дю, у-дю-дю-дю-дю, баю-баюшки баю, не ложися на краю. Там трое то ли пленных, то ли, мва-ха-ха, безвозвратных потерь. Ладно, мы поскакали, гон все-таки. + + + +Лось большими скачками скрылся в лесу. + + + +- Ну, брат Енот, пошли брать в плен, если осталось кого, - подытожил Медведь. + + + + + +****** + + + +- Итак, подведем результаты, - начал Медведь. + + + +Амур сиял на солнце, ветер гудел в кедрах, на сопке было тепло и ветренно. Среди трофеев нашлась зрительная труба и Косолапый был наверху блаженства. Партизанский отряд собрался на вершине каменной горки на том самом месте, где позавчера они получили страшное известие о браконьерах. + + + +- Берта и Волк - один, покалечен. + +- Жить будет, - заверил Волк. + +- Любить - вряд ли, - оскалившись во всю пасть, добавила Берта. + +- Тигр - один, тело не найдено, - продолжал Медведь. + +- Эти кабаны... - досадливо поморщился Полосатый, - Впрочем, он вернулся к земле, чего еще может желать Совершенномудрый... Кому-нибудь другому. + +- Заяц - один, целехонький. Молодец, Косой, не ожидал. + +- Служу... + +Донельзя гордый собой Заяц несколько секунд подыскивал, кому же он служит, затем рявкнул то, что не раз слышал от пограничных собак. + +- Служу России! + + + +Все на минуту замерли по сидке "смирно". + + + +- Рыся и Ирис - один. Мертвый. + +- Мы ни при чем, - оторвался от умывания Русь, - он сам свалился с дерева и шею свернул. + +- Ага, после того, как ты забрался туда и поточил когти об его ветку. Ладно, проехали, искать их не будут. Ну и троих мы с лосями. Двух они затоптали совсем, а один вроде ничего. Вот его и допросим. Тигр, давай его сюда. + + + +Еще позавчера Полосатый за такой тон вцепился бы Медведю в толстую морду, но теперь лишь хлестнул себя по бокам хвостом и притащи за шиворот зажмурившегося и тихо подвывающего мужика. + + + +- Где-то у меня был разговорник, - кряхтя поднялся медведь, - Серый, куси его там, чтобы глаза открыл, только не до смерти... + + + + + +***** + + + + + +Василий Дормидонтович Плюев, браконьер со стажем, сквозь ватные штаны почувствовал мощные челюсти на своей заднице и, взвизгнув, открыл глаза. Прямо на него в упор смотрела огромная медвежья харя. Харя открыла пасть, обдав Плюева зловонием и продемонстрировав ужасные желыте клыки, и хрипло произнесла: + + + +- Превед, кросавчег. + + + +© И. Кошкин diff --git "a/personal/creative/humor/Ода \"Калине\".md" "b/personal/creative/humor/Ода \"Калине\".md" new file mode 100644 index 00000000..e2ac51c5 --- /dev/null +++ "b/personal/creative/humor/Ода \"Калине\".md" @@ -0,0 +1,127 @@ +То не ветер в поле дует, поднимая вихрем пыль.  + +Это мчится по дороге, скоростной автомобиль!  + +Под капотом - V12, в задних фонарях - ксенон,  + +И в торпеду ловко врезан видеомагнитофон.  + + + +За рулем - Геннадий Калов, знаменитость и герой  + +Все окрестные девчонки бегают за ним толпой.  + +Что ж за парень этот Гена? Отчего такой успех?  + +Гена ездит на "Калине"! От того он круче всех!  + + + +Гена едет по проспекту, музыка в окно звучит,  + +И восторженные взгляды привлекает как магнит.  + +Подъезжает к светофору, тормозят машины все.  + +Глядь: какой-то сраный Опель на соседней полосе.  + + + +"Эй!",- из Опеля чудила замахал рукой в окно -  + +"Ставлю сотню, что объеду на своей твое говно!"  + +"Да легко!" - в ответ Геннадий, только сдвинул кепку вбок,  + +"Я тебе, инопомойщик, преподам сейчас урок!"  + + + +Загорается зеленый! Рев мотора, визг и свист,  + +И... глотает дым покрышек офигевший опелист!  + +Гена добрый, хоть и быстрый. Милосерден, хоть и крут.  + +Неспеша припарковался. Ждет...проходит пять минут.  + + + +Подъезжает к Гене Опель. "Да! Респект тебе, чувак!  + +Слышал, что "Калина" едет, но не знал, что едет ТАК!!!  + +Что же, сто рублей по праву заслужил, братэлло, ты!  + +Подскажи, как оказался за рулем такой мечты?"  + + + +"Не грусти!", - сказал Геннадий, "С этим нет проблем сейчас,  + +Ведь серийно выпускает эти тачки "АвтоВАЗ".  + +"Раньше ездил на трамвае, в душном офисе сидел,  + +"Потным лохом", издеваясь, обзывал меня отдел.  + + + +Есть у них у всех машины: "Киа", "Дэу" и "Хендай".  + +На работе все на тачках. я ж, как лох, плетусь в трамвай.  + +Но однажды зырю телик, начался рекламный блок.  + +Вижу: "Город наш влюбился". Значит есть на свете Бог!  + + + +Он мои мольбы услышал, снизошел на благодать,  + +Именно такой машиной я хотел бы обладать!  + +Красота и динамичность, Управляемость, комфорт.  + +Это вам не сраный "Лансер". И не раздолбайка "Форд".  + + + +С той поры, как подменили. Потерял покой и сон,  + +Только выдали зарплату - я бегом в автосалон.  + +Там, с кредитом от "Сбербанка" я мечту свою купил,  + +Затонировал, сигналку и фаркоп установил".  + + + +И с тех пор, как прочь отъехал я от дилерских ворот,  + +В жизни у меня случился истинный переворот!  + +Подъезжаю я к конторе - собирается народ,  + +Все глазеют на "Калину", изумленье так и прет!  + + + +"Ай да Гена! Вот так тачка, молодец, мужик, респект!"  + +"Сразу видно чувство стиля и недюжий интеллект!"  + +И не вру! - но в перерыве, как минутка удалась,  + +Мне в приемной секретарша на копире отдалась."  + + + +"Мдааа..." - взохнул печально опель. "Мне бы также зажигать!  + +Все! Пошел свою помойку на базаре продавать!"  + +"А продам - куплю "Калину", так как твердо понял я:  + +Есть на свете две машины: ВАЗ и прочая фигня!" diff --git a/personal/creative/humor/Сталин и атомная бомба.md b/personal/creative/humor/Сталин и атомная бомба.md new file mode 100644 index 00000000..f1909053 --- /dev/null +++ b/personal/creative/humor/Сталин и атомная бомба.md @@ -0,0 +1,139 @@ +1. +- Ебать-колотить! - сказал Сталин. - Атомная бомба! Берия скромно потупил глазки. Курчатов немного запоздал, но тоже потупил и стал нервно дергать себя за бороду. +- Это что же, взрывается? - спросил Сталин, постучав трубкой о железный бок бомбы. +- Еще бы, товарищ Сталин! - ответил Курчатов, чувствуя, что вопрос направлен к нему. - Как… Как даст! +- Как пизданет! - поддержал Берия. +- Вот ведь придумали, - сказал Сталин, затягиваясь. - А что, сильно взрывается? +- Я же говорю, товарищ Сталин - как даст! +- Как пизданет! - снова поддержал Берия и сделал за очками страшные глаза, как бы говоря, что уж если пизданет, так в самом деле пизданет. +- А что думает товарищ Жуков? +Жуков вроде задремал, но вовремя очнулся. +- Полезная вещь, - сказал маршал, звеня орденами. - Армии нужная. +- Так, - мудро заметил Сталин. - Но надо ее испытать. Да, товарищ Ворошилов? +- Обязательно, Коба, - поддержал железный нарком. +- А на ком? На жидах? +- На жидах не стоит, - смело сказал Берия. - Мы на них уже газы испытывали. Как-то нечестно получается. +- В самом деле, в самом деле, - задумался Сталин, выпуская паровозные клубы дыма. - Обидеться могут евреи, да. Спасибо товарищу Берия за своевременное замечание. Вы, кстати, сами-то не жид будете, товарищ Берия? Сталин, как водится, удачно пошутил, и все засмеялись. Смеялись, потрясая бородами, Калинин и Курчатов, хихикал Жуков, хохотал Ворошилов, а Берия тонко улыбнулся и сказал: +- Нет, товарищ Сталин, я мингрел. +- Мингрел - хуй на печке грел, - еще раз удачно пошутил Сталин, и общество еще немного посмеялось. Наконец Сталин звякнул трубкой по бомбе и заметил: +- Посмеялись - это хорошо, но мы не решили, на ком испытывать нашу атомную бомбу. +- На японцах? - вопросительно спросил Ворошилов. - Японцев пленных у нас много, да и война еще не кончилась, новых наловим. Немцы с той войны тоже еще остались. +- Немцы с японцами народ испытанный, - сказал Сталин. - Немцев с японцами мы уже били. Атомную же бомбу надо испытать на какой-то нации, которая нами со стороны бития еще не изучена. Правильно говорю, товарищ Курчатов? +Хуй бы Курчатов возразил. + +2. +Бомбу положили в самолет и повезли бросать. В самолете сидели пилоты: трижды Герой Советского Союза товарищ Кожедуб и трижды Герой Советского Союза товарищ Покрышкин. Командиром экипажа был Кожедуб, потому что он во время войны сбил 62 самолета противника, а Покрышкин - всего 59. Поэтому Кожедуб грубо сказал Покрышкину, беседовавшему с техниками: +- Хорош пиздеть, товарищ Покрышкин! Пора лететь бомбу бросать! +- Извини, товарищ Кожедуб, - сказал Покрышкин и залез в самолет. +Кожедуб повернул ключ зажигания, нажал на педаль, дернул рычаги, и краснозвездный самолет понесся по бетонной полосе секретного аэродрома. +- Вот и улетели, - сказал Ворошилов, смахнув слезу. +Самолет летел очень высоко. Кожедуб даже задремал от однообразия пейзажа за окном, а Покрышкин со скуки стал читать стихи: +- Поздняя осень. Грачи улетели. +Лес обнажился, поля опустели. +Только не сжата полоска одна. +Грустную думу наводит она... +- Что за стихи читаешь, товарищ Покрышкин? - спросил, не открывая глаз, Кожедуб. +- Товарища Сталина стихи, чьи же еще, - ответил Покрышкин. +- Ну-ну. Ты порули пока, а я вздремну. Лететь-то далеко еще? +- Километров тысячу пятьсот, - сказал Покрышкин, сверившись по карте. +- Ну и подремлю. Разбуди, как бомбу кидать станешь. +"А вот хуй тебе", - подумал Покрышкин мстительно. + +3. +Бомбу решили сбросить на Америку, в этом была великая идея товарища Сталина. Но он никак не мог догадываться, что Трумэн тоже построил атомную бомбу, и везут ее как раз сбросить на СССР. Даже не на СССР, а на Москву, чтобы убить товарища Сталина и Политбюро. Но Трумэн никак не мог догадываться, что товарищ Сталин и Политбюро уже уехали в большое подземное бомбоубежище, которое построили как раз в надежде, что сука Трумэн-таки решит сбросить атомную бомбу. Самолет под управлением американских пилотов "Энола Гей" летел как раз навстречу советскому самолету. "Энола Гей" назывался так потому, что на борту его летели пидарасы. Это не удивительно, потому что в Америке всегда было много пидарасов, так что найти подходящих среди летчиков не составило труда. В этом была тонкая издевка Трумэна над советским народом. Мол, прилетят американцы, сбросят бомбу и погибнет Сталин и все Политбюро. Русский народ наутро узнает об этом из газет и скажет друг другу: "Слыхали? Американцы-то товарища Сталина разбомбили и все Политбюро с ним!" - "Вот пидарасы!" А там и правда пидарасы. Вот же обидная оказия какая. В общем, пидарасы летели навстречу советскому самолету Кожедуба и Покрышкина, слушая Глена Миллера, а советские пилоты о том никак не подозревали. Курс самолетов пересекался примерно над Японией. + +4. +- Смотри, товарищ Кожедуб! - сказал Покрышкин, толкая командира ногой в бок. - Смотри: самолет! +- Это еще что такое? - удивился Кожедуб, открыв глаза. В самом деле, навстречу летел большой самолет, с виду американский. +- Это американцы летят, товарищ Кожедуб, - усилил его подозрения Покрышкин. - В нашу сторону летят! А за нами - Москва! +- Ну-ка мы у них спросим, - принял решение Кожедуб и открыл форточку. +- Эй, мужики! - заорал он по-американски, высунув голову. - Куда летим? +"Так мы ему и скажем", - подумали американцы, а вслух сказали: +- Да так. Катаемся. +- Мы тоже, - соврал Кожедуб, а сам отметил, что под хвостом американского самолета подвешена здоровая бомба, небось атомная. Спрятавшись внутрь, он сказал Покрышкину: +- Атомную бомбу везут, пидарасы. Звони товарищу Сталину. + +5. +Товарищ Сталин сидел в подземном бомбоубежище и спорил с Ворошиловым, пизданет бомба или не пизданет, когда подошел телефонист и сказал: +- Вас, товарищ Сталин. +Сталин строго спросил: +- Кто? +- Это Покрышкин, товарищ Сталин! - торопливо заговорил в трубке пилот. - Тут американцы, товарищ Сталин! Они, пидарасы, тоже бомбу везут! +- Что же вы стали над ней в отупении? - спросил Сталин. +- Да мы летим, товарищ Сталин! Просто задержались. Как-то нехорошо получается… +- Мда… Ну, погодите там немного, я посоветуюсь с товарищами. +Прикрыв ладонью трубку, Сталин сказал: +- Покрышкин звонит. Говорит, американцы тоже бомбу везут, пидарасы. +- Сбить, - сказал Ворошилов. - Сейчас истребительный полк поднимем. +- Все бы тебе сбить, - сказал ворчливо Сталин. - Это и дурак может. +Давай-ка вот что. Наберите мне по второму телефону Трумэна. А сам трубку открыл и говорит Покрышкину: +- Вы там их задержите ненадолго, а мы решение примем. +- Слушаюсь, товарищ Сталин! + +6. +Покрышкин закрыл трубку ладонью и сказал Кожедубу: +- Пизди что хочешь, товарищ Кожедуб, а надо американский самолет задержать. Товарищ Сталин решение принимает. Кожедуб опять высунулся в форточку и говорит: +- Эй, мужики! А что это у вас под хвостом висит? +- Бомба, - говорят американцы. +- А чего большая такая? +- Да так, - отвечают американцы, - просто. А у вас? +- И у нас тоже, - говорит Кожедуб. +Внизу японцы собрались, думают, что такое. Стали из зенитных орудий стрелять, да все зря - самолеты-то высоко. Император Хирохито позвал камикадзе, велел им лететь и вражеские самолеты сбить. Но камикадзе, известное дело, пока ритуальные песни спели, сакэ выпили, повязки повязали… Японцы, одно слово. + +7. +Тем временем Сталина соединили с Трумэном. +- Алло, мистер Трумэн? +- Мистер Сталин? Как здоровье, как погода? +- Вашими молитвами. Тут вот какое дело - самолетик наш один летел… над Японией… +- Ну так на здоровье. +- А тут ваш навстречу… +- Так война у нас с ними. +- А бомба зачем? +- Так война у нас с ними! У вас тем более тоже бомба. +Американцы-пидарасы Трумэну тоже позвонили, ясное дело. +- Так война у нас с ними, - сказал Сталин. - Сейчас бомбу на них и бросим. +- Так бросайте. +- Нет уж, вы первые! +- А вы обманете. +- Мы?! Хоть раз обманули?! +- Да до хуя. +- И вы до хуя. +- А вы коммунисты, коммунистам верить нельзя. +- А вы буржуи, буржуям верить тоже нельзя, еще Ленин так велел. +- Ваш Ленин мудак! +- А ваш Линкольн жид и пидарас, а Вашингтон гондон, а Джефферсон долбоеб, а своей конституцией жопу подотрите в день независимости. И вообще, пошел на хуй. Сейчас трубку положу, - мудро сказал Сталин. На это Трумэн не нашелся что сказать и помолчал. +- Хорошо, мистер Сталин, - сказал он. - Мы бомбу бросим. Но и вы бросайте! +- Бросим-бросим, - заверил Сталин. + +8. +Американцы в самом деле бросили бомбу и полетели домой. Бомба упала и как пизданет! Наутро японцы по всей стране вышли читать газеты и говорят друг другу: "Слыхали? Американцы на Хиросиму бомбу кинули!" - "Вот пидарасы!". +А там и правда пидарасы. Вот же обидная оказия какая, даже для японцев, даром что желторылый народец. +А наши дальше летят над Японией. +Тут Трумэн звонит Сталину и сердито так говорит: +- Алло! Мистер Сталин! Это нечестно! +- Наебал! Наебал! - обрадовался Сталин. +- Это недостойно, - говорит Трумэн. +- Мало ли. +- Мы так не договаривались. +- И хули? +- Вы тоже обещали бомбу бросить! +- А не бросили. Щас на вас скинем, ха-ха! +- Тогда мы на вас другую бросим! У нас еще есть! +- Секунду, мистер Трумэн, мне надо посоветоваться с Политбюро, - сказал Сталин, посерьезнев. +- Очень даже может быть, что у них вторая бомба есть, - сказал Берия, выслушав. +- А у нас разве нет? +- Пока нет, товарищ Сталин, - испугался Курчатов. - Но скоро сделаем! +- Поздно, - махнул рукой Сталин. - И что будем делать? +- Давайте там и сбросим. Я ж говорил, на японцах испытаем, - предложил Ворошилов. +- Ну ладно. Только не сразу. Пускай Трумэн там немного пообсирается, - сказал Сталин и снова связался с Трумэном. +- Алло, мистер Трумэн? В общем, мы думаем. + +9. +Краснозвездный самолет кружил на Японией, вырабатывая остатки горючего. Трижды геройский экипаж доедал последнюю банку тушенки, когда с земли наконец пришло сообщение: "Хуячьте где поближе. Сталин". +- Что там у нас? - обрадовался небритый Кожедуб, подтягивая поближе карту. - Так… Осака… Йокосука… Нагасаки… +- Что-то у них все суки да ссаки, - брезгливо сказал Покрышкин, облизывая банку изнутри. - Какая там ссака последняя была? +- На-га-саки, - прочел Кожедуб. +- Вот и пизда им, - сказал Покрышкин, нажимая кнопку. + +10. +Так в 1945 году не случилось атомной войны между двумя великими державами. diff --git a/personal/creative/poems/В чем причина слез.md b/personal/creative/poems/В чем причина слез.md new file mode 100644 index 00000000..964b6f8f --- /dev/null +++ b/personal/creative/poems/В чем причина слез.md @@ -0,0 +1,77 @@ +От чего ты плачешь? + +В чем причина слез? + +Может быть тот Мальчик + +Грусть тебе принес? + + + +Он любил и клялся + +В верности тебе + +После вы расстались  + +Сами по себе.. + + + +Ты наверно веришь + +Всем его словам.. + +Ты все это стерпишь + +Боль уйдет к годам.. + + + +Ты уже не будешь + +Той простою Ей. + +Но ты не забудешь + +Тех счастливых дней. + + + +Расскажи котенок, + +В чем причина слез? + +Ты ведь не ребенок! + +Это не всерьез! + + + +Разбегутся мысли, + +Повернет река.. + +Тучи, что нависли + +Пустятся в бега.. + + + +Ты найдешь другого, + +Вновь придет любовь.. + +Пусть и не такого, + +Но ведь все же Вновь.. + + + +В жизни канут море + +Миллионы грёз + +И поймешь ты вскоре + +В чем причина слез... \ No newline at end of file diff --git a/personal/creative/poems/Видишь, там на горе.md b/personal/creative/poems/Видишь, там на горе.md new file mode 100644 index 00000000..2c8620a3 --- /dev/null +++ b/personal/creative/poems/Видишь, там на горе.md @@ -0,0 +1,43 @@ +У толчка полоскался алкаш Алексей, +А Вован не хотел за бухлом. +Алексей извергал из себя пескарей, +Нямыч ныл о бутылке с вином.. + +Алексей вдруг изрек: «Я покину толчек, +Если ты мне наполнишь бокал!».. +Ну а Нямыч налил до краев в свой бокал +Тем чем только что долго блевал… + +-Видишь, там на горе-еее.. +Возвышается дом! + +А в нем десяток ребят.. +Заливают бухло-ом! + +Я там помнится на-ам +Эту дрянь покупал… +Пошли в магазин, пошли в магазин, пошли в магазин вдвое-ем! + +…. + +-Но Вован, в унитазе блястит блевота, +Черный муха кружит над толчком! +Пожалей ты меня, принеси мне бухла, +А магАзин оставь напотом… + +Охуел тут Вовчик и стукнул всердцах +По глади ванны рукой.. +-Ты верно слабак! +..И Алёша в слезах +Продолжил плеваться едой….. + +-Видишь, там на горе-еее.. +Возвышается дом! + +Пошли, там купим пивка-аа +Побухаем, споё-ом.. + +А когда надое-ест +Побежим от ларька.. + +Блевать под забор, Блевать под забор, Блевать под забор вдвое-ем! \ No newline at end of file diff --git a/personal/creative/poems/Колонча.md b/personal/creative/poems/Колонча.md new file mode 100644 index 00000000..befee470 --- /dev/null +++ b/personal/creative/poems/Колонча.md @@ -0,0 +1,305 @@ +Бывает быстро и случайно, + +Когда ее совсем не ждешь + +Любовь нагрянет невзначай, но + +К кому любовь – не разберешь.. + + + +Бывало, хочешь всех и сразу, + +Бывало, не влечет никто, + +Бывали стервы и заразы.. + +Но!.. не случался дед-пихто! + + + +О нет, ну что вы!  Я не гомик! + +Я не люблю дедов и баб! + +Я просто мирный тихий гномик.. + +Люблю котов, курей, собак..  + + + +Ну накрайняк и мокрощелку + +Могу забацать невзначай.. + +Однако лучшая девчонка + +Во всей деревне – Колонча.. + + + +Ее прозвали Тетей Степой, + +Она у кладбища жила.. + +И то и дело, ненароком, + +У мертвецов в рот хуй брала.. + + + +И так, было, возьмет умело, + +Так перекрутит, засосет, + +Что половину хладнотелых + +Буквально прям и затрясет! + + + +Боялись девку парни наши.. + +Не только мертвых воскрешать.. + +Умела.. как ее! Наташа! + +Но и живых ртом умерщвлять! + + + +И до сих пор все слухи ходят, + +Что некрофилов по утрам + +На кладбище глухом находят + +Усохших, мертвых! Тут и там! + + + +То дохлую давно уж ведьму  + +Желают в ротик отодрать, + +Те что хотят (как по поверью) + +Себя ко Дьяволу продать! + + + +… + + + +Живу я щас на тихой даче, + +Прям в доме био-туалет! + +Валяюсь и дрочу в придачу + +Уже каких то тыщу лет.. + + + +История давно начАлась + +По меркам Пушкина – Пиздец! + +Ты бы подруга обкончалась,  + +Если б услышала конец! + + + +А началось все в том далеком.. + +Тыща пятнадцатом году, + +Когда заехал в невысокий, + +Но славный домик. На пруду. + + + +Однако ж вскоре возле дома + +Нашел я человека труп.. + +Мне ситуация знакома! + +Не раз таких ложил под сруб.. + + + +Ну думаю, труп сам тут умер.. + +Стоял, упал и охладел. + +Но думаю, какого хуя, + +Этот мудак сортир задел?! + + + +Забыли. Сбросил труп в болото. + +Его там быстро не нашли.. + +Но что-то стало мне охото + +Потрахать бабу на печи! + + + +И стал искать я кандидатку.. + +Животных хуй терпеть не мог.. + +Увидел вдруг старушку-бабку.. + +Совсем все спало между ног! + + + +Но чу! Когда я отвернулся, + +Я услыхал медовый глас! + +Ойой! Голубчик! Ты б вернулся! + +Я берегла себя для Вас! + + + +Я охуел от тех наездов! + +Ебать бабульку  - Во дела! + +Но так меня тоска по сЕксу + +Под это время заебла, + + + +Что развернулся.. И опешил! + +Вместо бабульки предо мной + +Стоит девченка, с кошку весом, + +Но с высоченной головой.. + + + +Признаца я любил стройнушек.. + +Но не таких же в самом де! + +Эх жалко нету с чаем плюшек.. + +Эк зачасалося в ялде!! + + + +Но девка вышла не простая.. + +Она мне сразу не дала.. + +Взяла под яйца как родная + +И молча к дому повела.. + + + +Разделась.. Красоваться стала.. + +Вздрочнула, словно не при мне.. + +Но! Мне потрогать не давала! + +Не подпускала, блядь, к себе! + + + +И так, и эдак я подлазил, + +И гладить пробовал, лизал! + +И спереди вставал и сзади! + +.. В конце концов мой хуй упал! + + + +Ну не дала мне, идиоту! + +И яйца щемит, что не в мочь! + +И побежал я на болото, + +Осуществить свой мирный дроч.. ☹ + + + +А баба, словно так и ждала, + +Вдруг похуярила за мной! + +Достала два больших кинжала + +И притаила за спиной.. + + + +Но я просек ее подлянку! + +Ебаться хоть и жуть хотел! + +И уж подумал: лесбиянка!.. + +Да как въебу ей в нос ногой! + + + +Бабища право охуела, + +От этой наглости со мной.. + +Но.. сразу на коленки села.. + +И потянулась за ялдой. + + + +С тех пор я стал ваще бессмертный + +Лишь только насморк заебал.. + +Да и мой хуй (он был конкретный!) + +С тех пор как кочерыжка стал… + + + +С тех пор ебу собак и кошек.. + +Ну бабок изредка каких.. + +Иль на панели мандавошек.. + +Хоть мой размер смущает их.. + + + +Но все же я скажу вам, други.. + +Быть может это сгоряча.. + +Но лучше всех блядей в округе + +Сосет (и дрочит) КОЛОНЧА! :)) + + + +(c) mallexxx \ No newline at end of file diff --git a/personal/creative/poems/Печаль, темная.md b/personal/creative/poems/Печаль, темная.md new file mode 100644 index 00000000..aa63cca2 --- /dev/null +++ b/personal/creative/poems/Печаль, темная.md @@ -0,0 +1,65 @@ +В моих глазах туман и.. пусто, + +И опустились руки – крылья птицы. + +А на душе темно, тоскливо, грустно.. + +И по ночам уже никто не снится.. + + + +Хотя полно друзей - родных и близких, + +Которые Сейчас живут, не искрой, + +Которой я сгорел недавно тихо + +И пал на дно душой, в ущелье низко.. + + + +Моя душа – как сигареты пепел, + +Остались в ней одни тоска и горесть.. + +..Который радостно сдувает ветер, + +И оголяет.. суку, совесть. + + + + + + + + + +pre: + +В моих глазах туман и.. пусто + +И часты боли в пояснице + +А на душе тоскливо, грустно.. + +Мне по ночам никто не снится.. + + + +Хотя полно друзей и близких, + +Которые Живут, не искрой, + +Которою сгорел я тихо + +И пал на дно душою низко.. + + + +Моя душа – как будто пепел, + +В ней лишь одни тоска и горесть.. + +..Который раздувает ветер, + +И оголяет.. суку, совесть. \ No newline at end of file diff --git a/personal/creative/poems/Человеческая.md b/personal/creative/poems/Человеческая.md new file mode 100644 index 00000000..d0861719 --- /dev/null +++ b/personal/creative/poems/Человеческая.md @@ -0,0 +1,53 @@ +Я б хотел жить в воде, + +Но не знаю кем быть: + +Я б наверно стал белым китом! + +Я бы плавал в морях  + +И глушил бы я рыб  + +Своим белым большим плавником! + + + +Или будь я змеёй, + +Я бы ползал в полях + +И пугал бы народ во дворе! + +И любить бы наверно  + +Так сильно, в сердцах, + +Я не смог бы так близко к земле... + + + +Или Вот! Я хочу  + +Черной птицою стать + +И.. Парить высоко над землей! + +Я б любил вас как Предков + +Любимых своих + +И любовь бы была неземной! + + + +Но увы, суждено мне  + +Земным умереть,  + +Человеком, как я не хочу! + +Песню я допою, + +Мне ведь Нужно допеть! + +Но потом непременно умру! \ No newline at end of file diff --git a/personal/gift-ideas.md b/personal/gift-ideas.md index 4f9522d2..23ae01cf 100755 --- a/personal/gift-ideas.md +++ b/personal/gift-ideas.md @@ -18,7 +18,7 @@ updated: 2026-05-17 ## Подарки -- [ ] Powerball +- [x] Powerball - [ ] Чугунок - [ ] Керамика: вазы, тарелки ручной работы - [ ] Гончарный круг diff --git a/personal/index.md b/personal/index.md index c05b7c5e..d18718ef 100755 --- a/personal/index.md +++ b/personal/index.md @@ -139,6 +139,7 @@ Hub: [[personal/projects/media-pipeline-tracker-parsers|Media Pipeline Roadmap]] - [[personal/projects/virfield|Virfield — macOS VM control plane]] — релиз, статус и каноническая документация. - [[docker_colima_setup|Docker Colima Setup]] +- [[personal/tech/macos-voom-cleanup|macOS — автоматическая очистка: Voom, Eagle, Docker и бэкапы]] - [[truenas-remote-access-reverse-proxy|TrueNAS Remote Access]] - [[Swift cross-platform testing|Swift Cross-Platform Testing]] - [[Dyld dynamic interposing|Dyld Dynamic Interposing]] diff --git a/personal/instructions/Выборочный бэкап Mac на Kraken.md b/personal/instructions/Выборочный бэкап Mac на Kraken.md new file mode 100644 index 00000000..ebbd0a5a --- /dev/null +++ b/personal/instructions/Выборочный бэкап Mac на Kraken.md @@ -0,0 +1,263 @@ +--- +title: Выборочный бэкап Mac на Kraken +updated: '2026-10-23' +tags: + - backup + - backrest + - restic + - macos + - kraken + - instructions +--- +# Выборочный бэкап Mac на Kraken + +## Текущая схема + +На Mac установлены Backrest 1.14.1 и restic 0.19.1. Backrest работает как служба пользователя macOS `sh.brew.backrest` через LaunchAgent и доступен по адресу . Его окно или браузер держать открытым не требуется; пользователь должен войти в macOS, Mac должен быть включён и иметь доступ к Kraken во время задания. Движок restic создаёт шифрованные снимки и отправляет их по SFTP. На Kraken нужен только SSH/SFTP-сервер; Backrest или restic на сервере не установлены и не требуются. + +Рабочий репозиторий Backrest: `kraken-mbp-black`. Адрес restic: + +```text +sftp:kraken@192.168.1.14:/srv/dev-disk-by-uuid-6194539b-03e0-4f33-986b-4da09834e82f/backups/mbp-black/restic +``` + +Сейчас используется прямой локальный IP `192.168.1.14`. За пределами домашней сети этот адрес недоступен; автоматическое переключение на VPN не настроено. Не следует считать ночное задание успешным, если Mac был вне локальной сети или спал. + +Конфигурация Backrest хранится в `/Users/admin/.config/backrest/config.json` с правами `0600`; она содержит пароль репозитория. Такой же пароль сохранён в связке ключей macOS: служба `backrest-kraken-restic`, учётная запись `admin`. Пароль не записан в этой заметке. Для восстановления после утраты Mac нужна отдельная копия пароля вне самого Mac; наличие такой копии пока не подтверждено. + +Для «Полного доступа к диску» macOS добавлены оба файла: + +- `/opt/homebrew/Cellar/backrest/1.14.1/bin/backrest` +- `/Users/admin/.local/share/backrest/restic` + +После обновления Backrest, если путь к бинарнику изменится, права доступа к диску нужно проверить повторно. + +## Планы и расписание + +Все три плана используют локальное время Mac и один репозиторий `kraken-mbp-black`. Для каждого плана настроено хранение **3 последних снимков** (`policyKeepLastN: 3`): после будущих запусков Backrest будет убирать более старые снимки этого плана из истории. Встроенная очистка `prune` запланирована **по воскресеньям в 06:00** по локальному времени Mac (`maxUnusedPercent: 10`); она освобождает место, занятое уже удалёнными снимками. Отдельного скрипта нет. [Backrest: Retention и Prune](https://garethgeorge.github.io/backrest/introduction/getting-started). + +### Домашняя папка — `kraken-home` + +Ежедневно в **03:00**. Источник: `/Users/admin`. Включён `--one-file-system`, поэтому другие тома не обходятся. Действуют 114 правил исключения ниже. Сохраняются документы, проекты, настройки и другие файлы домашней папки, не подпавшие под эти правила. Общего исключения `.git` нет. + +### Выборочные системные файлы — `kraken-system` + +Ежедневно в **03:30**. Включён `--one-file-system`. Источники в текущей конфигурации: + +```text +/Library/Audio/Impulse Responses +/Library/Fonts +/Library/LaunchDaemons/ai.virfield.lume.plist +/Library/LaunchDaemons/ai.virfield.virfieldd.plist +/Library/LaunchDaemons/com.cloudflare.cloudflared.plist +/Library/LaunchDaemons/top.eagle.wg-auto.plist +/Library/WebServer/Documents +/Users/Shared/obsidian +/opt/homebrew/etc +/private/etc/apache2 +/private/etc/hosts +/usr/local/bin +``` + +Для этого плана действуют 37 правил исключения ниже. + +### PostgreSQL — `kraken-postgresql` + +Ежедневно в **04:30**. Снимок создаётся потоком команды `pg_dumpall` без промежуточного локального файла: + +```text +--stdin-filename postgresql-all.sql --stdin-from-command -- /opt/homebrew/bin/pg_dumpall -l postgres +``` + +В снимке находится один логический дамп `postgresql-all.sql`, включая базы и глобальные объекты PostgreSQL. Для восстановления его нужно подать в `psql` на подготовленном сервере; полноценное восстановление этого дампа пока не проверено. Старые локальные дампы `/Users/admin/.local/share/mbp-black-postgresql` исключены из домашнего плана. + +## Правила исключения + +Это точные правила из действующей конфигурации Backrest на 23 октября 2026 года. Перед изменением правил проверяйте список в интерфейсе Backrest; конфигурация может измениться позже этой записи. Каталоги вроде `node_modules`, `.venv`, кэши, сборочные артефакты, установочные образы и lock-файлы не попадают в снимки. Маски с `**` действуют на любой глубине. + +### `kraken-home` — 114 правил + +```text +/Users/admin/DuckDuckGo +/Users/admin/Downloads +/Users/admin/.colima +/Users/admin/Library/Developer +/Users/admin/Library/Caches +/Users/admin/.local/share/balda-workspace/backups +/Users/admin/.virfield-v2/cache +/Users/admin/go/pkg/mod +/Users/admin/.npm +/Users/admin/.gradle/caches +/Users/admin/.gradle/daemon +/Users/admin/.gradle/wrapper/dists +/Users/admin/.swiftpm/cache +/Users/admin/Library/pnpm/store +/Users/admin/.cache/uv +/Users/admin/*/**/.gradle +/Users/admin/Developer/**/build +/Users/admin/Developer/**/Build +/Users/admin/Developer/**/dd +/Users/admin/Developer/personal-os/tools/rlm-lab/.runtime/models +/Users/admin/Developer/personal-os/tools/rlm-lab/.runtime/uv-cache +/Users/admin/Developer/personal-os/tools/rlm-lab/.runtime/deno-cache +/Users/admin/Developer/personal-os/tools/rlm-lab/.runtime/npm-cache +/Users/admin/Library/Application Support/Claude/Cache +/Users/admin/Library/Application Support/Claude/Code Cache +/Users/admin/.Trash +/Users/admin/Library/Application Support/Steam/steamapps/common +/Users/admin/.android/avd +/Users/admin/Library/Android/sdk +/Users/admin/.ollama/models +/Users/admin/.cache/huggingface/hub +/Users/admin/Library/Application Support/Google/Chrome/OptGuideOnDeviceModel +/Users/admin/docker/balda-mm-mac/state-failed-upstream-* +/Users/admin/docker/balda-mm-mac/state/setup-backups +/Users/admin/Library/org.swift.swiftpm/swift-sdks +/Users/admin/.vscode/extensions +/Users/admin/.cursor/extensions +/Users/admin/Library/Containers/com.apple.CoreDevice.CoreDeviceService/Data/Library/Caches +/Users/admin/haos/haos_generic-*.img +/Users/admin/haos/haos_generic-*.img.xz +/Users/admin/Library/Application Support/Dash/DocSets +/Users/admin/Library/Application Support/Code/CachedExtensionVSIXs +/Users/admin/Library/Application Support/Google/GoogleUpdater/crx_cache +/Users/admin/Library/Logs +/Users/admin/Library/Application Support/com.apple.wallpaper/aerials/videos +/Users/admin/whisper-env +/Users/admin/Library/Application Support/Claude/vm_bundles +/Users/admin/Developer/dyld_shared_cache_* +/Users/admin/Developer/Big Sur Libraries +/Users/admin/Library/Application Support/Asana/Cache +/Users/admin/Library/Application Support/Asana/Code Cache +/Users/admin/Library/Application Support/Asana/Service Worker/CacheStorage +/Users/admin/Library/Application Support/YandexMusic/Cache +/Users/admin/Library/Application Support/YandexMusic/Code Cache +/Users/admin/Library/Application Support/Spotify/PersistentCache +/Users/admin/Library/Application Support/Code/Cache +/Users/admin/Library/Application Support/Code/logs +/Users/admin/Library/Application Support/Code/CachedData +/Users/admin/Library/Application Support/Code/Crashpad +/Users/admin/Library/Application Support/Mattermost/Cache +/Users/admin/Library/Application Support/Mattermost/Code Cache +/Users/admin/Library/Containers/Mattermost.Desktop/Data/Library/Application Support/Mattermost/Code Cache +/Users/admin/Library/Containers/com.apple.mediaanalysisd/Data/Library/Caches +/Users/admin/Library/Application Support/zoom.us/data/UnifyWebView_Cache +/Users/admin/Library/Application Support/Steam/Steam.AppBundle +/Users/admin/Library/Application Support/Steam/appcache +/Users/admin/Library/Application Support/Steam/depotcache +/Users/admin/Library/Application Support/Steam/logs +/Users/admin/.cache/codex-runtimes +/Users/admin/.local/share/claude/versions +/Users/admin/.local/share/cursor-agent/versions +/Users/admin/.local/share/uv/python +**/media_files_db/out +**/media_files_db/cache +/Users/admin/Virtual Machines.localized/Windows 95*.utm +/Users/admin/Library/Containers/com.duckduckgo.macos.browser.debug +.build +.build-* +DerivedData +deriveddata +derviveddata +.deriveddata +.dd +WebKitBuild +node_modules +.venv +venv +__pycache__ +.pytest_cache +.mypy_cache +.ruff_cache +.next +.nuxt +.turbo +.parcel-cache +.cxx +.kotlin +**/Carthage/Build +*.pyc +*.pyo +.DS_Store +._* +*.dmg +*.xip +*.lock +*.lockb +Package.resolved +package-lock.json +npm-shrinkwrap.json +pnpm-lock.yaml +pnpm-lock.yml +packages.lock.json +*.pgdump +/Users/admin/.local/share/mbp-black-postgresql +``` + +### `kraken-system` — 37 правил + +```text +.build +.build-* +DerivedData +deriveddata +derviveddata +.deriveddata +.dd +WebKitBuild +node_modules +.venv +venv +__pycache__ +.pytest_cache +.mypy_cache +.ruff_cache +.next +.nuxt +.turbo +.parcel-cache +.cxx +.kotlin +**/Carthage/Build +*.pyc +*.pyo +.DS_Store +._* +*.dmg +*.xip +*.lock +*.lockb +Package.resolved +package-lock.json +npm-shrinkwrap.json +pnpm-lock.yaml +pnpm-lock.yml +packages.lock.json +*.pgdump +``` + +## Проверенный результат + +На 23 октября 2026 года созданы снимки всех трёх планов. Системный снимок завершился успешно; `/private/etc/hosts` восстановлен и совпал побайтно. PostgreSQL-снимок завершился успешно; `postgresql-all.sql` имеет размер 26 485 621 820 байт, однако полное восстановление базы не проверялось. + +Домашний снимок `25ecd67531c890fcbe76f4142d189fa369e33be7a5aaf3be76f99e85f20f8d59` сохранён с предупреждением: обработано 448 705 файлов, 21 защищённый служебный файл macOS не удалось прочитать даже после выдачи Full Disk Access. Ошибки относятся к `com.apple.milod`, индексу `CoreSpotlight/NSFileProtectionComplete` и одному файлу настроек `group.com.apple.secure-control-center-preferences`. Два других файла из домашнего снимка восстановлены и совпали с оригиналами побайтно. + +Команда `restic check` проверила структуру репозитория и все четыре снимка без ошибок. Она сообщила о 192 дополнительных файлах пакетов после прерванных попыток; это не ошибка целостности. Лимит трёх версий и расписание `prune` установлены после этой проверки; их первый автоматический запуск ещё не проверен. + +В папке назначения `/srv/dev-disk-by-uuid-6194539b-03e0-4f33-986b-4da09834e82f/backups/mbp-black/` находится только `restic/`. + +## Проверка и восстановление + +Для обычной работы откройте , выберите нужный план и снимок. По завершении ночного задания проверьте его статус: **Success** или **Warning/Error**. Домашний план сейчас показывает **Warning** из-за 21 недоступного файла; этот статус не следует принимать за полностью успешный бэкап. + +Для восстановления отдельных файлов выберите снимок в Backrest, найдите файл и восстановите его в отдельный каталог, затем сравните с оригиналом перед заменой. Для аварийного восстановления без Backrest можно установить restic, подключиться к указанному SFTP-репозиторию и использовать пароль репозитория. Без пароля содержимое снимков не прочитать. + +Проверка через терминал на этом Mac (пароль берётся из связки ключей, в команду и заметку не записывается): + +```sh +RESTIC_REPOSITORY='sftp:kraken@192.168.1.14:/srv/dev-disk-by-uuid-6194539b-03e0-4f33-986b-4da09834e82f/backups/mbp-black/restic' +restic --password-command 'security find-generic-password -a admin -s backrest-kraken-restic -w' -r "$RESTIC_REPOSITORY" snapshots +restic --password-command 'security find-generic-password -a admin -s backrest-kraken-restic -w' -r "$RESTIC_REPOSITORY" check +``` + +Для восстановления целого снимка выберите в Backrest отдельную папку назначения с достаточным свободным местом. Не восстанавливайте сразу поверх работающей системы или базы PostgreSQL. diff --git a/personal/plans/lampac-watched-to-watchlist.md b/personal/plans/lampac-watched-to-watchlist.md index 598da83a..53e76e8e 100755 --- a/personal/plans/lampac-watched-to-watchlist.md +++ b/personal/plans/lampac-watched-to-watchlist.md @@ -3,8 +3,8 @@ title: Отметка «просмотрено» из Lampa → watchlist.md type: plan status: implemented-bug-open-markwatched-insert namespace: personal -created: 2026-09-23T00:00:00.000Z -updated: '2026-09-27T00:00:00.000Z' +created: '2026-09-23T00:00:00.000Z' +updated: '2026-10-30' tags: - lampac - lampa @@ -17,10 +17,19 @@ related: - '[[family/projects/watchlist-automation]]' - '[[personal/plans/lampac-watchlist-plugin]]' - '[[family/documents/movies-watchlist]]' + - '[[personal/tech/lampac-truenas]]' --- # Отметка «просмотрено» из Lampa → `movies-watchlist.md` +## Обновление источника истории — 2026-10-30 + +Активная TimeCode перенесена на TrueNAS; все 9 записей сохранены. Kraken читает общий HTTPS dump API через приватную переменную `LAMPAC_TIMECODE_DB`; поддержка API реализована и запушена в [watchlist-sync, 3810d77](https://github.com/mallexxx/watchlist-sync/commit/3810d77). Код развёрнут на Kraken, dry-run прочитал9 записей без изменений списка. + +Единственный писатель Obsidian и генератор каталога остаётся на Kraken; на NAS публикуются готовые JSON. Синхронизации SQLite нет. Локальная SQLite Kraken сохранена для отката и больше не является активным источником ночного шага. Реальная API-запись и сохранность прогресса проверены до/после NAS-бэкапа. + +Актуальная схема, права WAL/SHM и бэкапы — [[personal/tech/lampac-truenas]]. Ниже сохранена история предыдущей реализации и её отдельных замечаний; это обновление меняет источник данных и не заявляет исправление всех исторических вопросов разметки списка. + > **Проблема (Alex, 2026-09-23):** «А у нас из лампы уходят отметки просмотрено в список галочками как было в jellyfin?» > Ответ: **нет**. Задача — сделать, чтобы уходили. diff --git a/personal/projects/virfield.md b/personal/projects/virfield.md old mode 100755 new mode 100644 index 00f88036..4666fc57 --- a/personal/projects/virfield.md +++ b/personal/projects/virfield.md @@ -2,7 +2,7 @@ Проверено: **2026-10-16** (практика UI-тестов — 2026-10-17, см. раздел ниже). Последний опубликованный stable Latest и установленная версия — [v2.1.1](https://github.com/mallexxx/virfield/releases/tag/v2.1.1), source `2c64888abed617722e79d618dd4911efa0c4b126`. Релиз опубликован после явного подтверждения владельца. Архив macOS ARM64 и SHA256SUMS скачаны с публичных URLs без авторизации; содержимое побайтно совпадает с проверенным установленным пакетом. Предыдущая публикация: [[personal/projects/virfield-release-v2.1.0]]. -Текущий local/GitHub `main`: **`2c64888abed617722e79d618dd4911efa0c4b126`**, checkout чистый, локально и на origin только `main`. Изменение исправляет обработку ошибки `launchctl bootstrap` уже зарегистрированного собственного guest Keychain agent: recovery разрешён только после проверки точного launchd job, затем остаются все прежние native readiness gates. Добавлены закрытый bootstrap diagnostic и regression с настоящим shell-блоком; контрфактический тест на старом коде воспроизвёл отказ. Make check/race, Staticcheck, module verify, Govulncheck и make check из распакованного точного кандидата прошли. +На 2026-10-16 local/GitHub `main` был **`2c64888abed617722e79d618dd4911efa0c4b126`**; актуальный локальный `main` и состояние production указаны в разделе «Текущая работа 2026-10-23» ниже. Изменение исправляет обработку ошибки `launchctl bootstrap` уже зарегистрированного собственного guest Keychain agent: recovery разрешён только после проверки точного launchd job, затем остаются все прежние native readiness gates. Добавлены закрытый bootstrap diagnostic и regression с настоящим shell-блоком; контрфактический тест на старом коде воспроизвёл отказ. Make check/race, Staticcheck, module verify, Govulncheck и make check из распакованного точного кандидата прошли. ## Установленная версия и проверка обновления @@ -57,11 +57,70 @@ macOS make check/build/Staticcheck прошли; настоящий read-only vo - Тайминги: prepare до `ready` ≈40 с для обеих VM; release → `released` ≈7 с (job сначала `delete_dispatched`, итоговое состояние читать из `vm_lease`, не ждать только `virfield_job`). - **Запись экрана**: на `uitest-27-v1` `screencapture -v` работает по обычному SSH; на macOS 12 (`developer-v1`) `capture error`, нет Screen Recording TCC, SIP включён. Если нужна запись на старых VM, нужен golden с заранее выданным разрешением. - **UI automation**: macOS 12 VM — Developer Mode уже включён, sudo без пароля, но `automationmodetool enable-automationmode-without-authentication` нужно выполнить; `ui_testing` проба помогла бы здесь. Для запуска из SSH нужна консольная GUI-сессия (Terminal `.command`). +- **`ready` не подтверждает готовность UI Automation — повторно 2026-10-21.** Две свежие VM, `monterey-xcode-13.4.1` (Tart, macOS 12.7.6/21H1320, ARM64, Xcode 13.4.1/13F100) и `htpc-sonoma-14-xcode-16.2` (node VM, macOS 14.8.9/23J631, Intel, Xcode 16.2/16C5032a), получили успешный prepare и lease `ready`. При этом `/usr/bin/automationmodetool` на обеих сообщил `Automation Mode is disabled` и `This device requires user authentication to enable Automation Mode`. Developer Mode был включён, `admin` входил в `_developer`, консольная сессия принадлежала `admin`. XCTest runner загрузился, но запуск через Terminal и напрямую через pinned SSH завершился `Timed out while enabling automation mode`, exit 65, **0 выполненных тестов**. `sudo automationmodetool enable-automationmode-without-authentication` затем сообщил успех; повторная проверка уже показывала `DOES NOT REQUIRE user authentication`, однако проверенный повтор по-прежнему завершился timeout до тестовых методов. Контрольные минимальные UI-тесты, собранные локально штатными SDK Xcode 13.4.1 на Monterey и Xcode 16.2 на Sonoma (без нового bundle и без shim), тоже завершились `Timed out while enabling automation mode`, exit 65, до тестового метода. Поэтому проблема ещё не закрыта, а совместимость исполнения нового bundle этим прогоном не доказана. Нужны явная проверка Automation Mode при UI-readiness, подготовка соответствующего UI-профиля и короткий настоящий XCUITest с ненулевым числом выполненных тестов; native GUI/Keychain readiness этого не заменяет. Leases: `lease-9ec9281c953de634a53572e293862901`, `lease-6994a85f7abcefd367dd40d45b08e87b`. +- **Уточнение UI-readiness, 2026-10-22:** последующая проверка через pinned SSH без Screen Sharing на новых leases `lease-202db9fd4686e87363ac66171657ba21` (Sonoma Intel/Xcode 16.2) и `lease-22c0cfb96e3253d3ed1f7d8ebee6ab77` (Monterey ARM/Xcode 13.4.1) выполнила настоящие native XCUITest probes: 1/1 на каждой OS. На Sonoma один и тот же CI test bundle выполнил PrintingTests 7/7 и с упакованным runtime, и со штатным XCTest 16. На Monterey исправленный AddressBarSearchQueryEditingTests прошёл 26/26 с первой попытки; полный сброс состояния подтверждён только перед первым методом (flags 1 + 25×0). Для старого XCTest потребовались завершение своего target app между процессами, подготовка autocomplete fixture через defaults и отключение вложенного синтеза событий из TCC polling timer. Это снимает утверждение, что UI Automation на этих образах вообще невозможно запустить, но не доказывает первопричину прежнего automation-mode timeout и не заменяет readiness probe в Virfield. Отдельный sandbox запуск Monterey завис в системных Keychain вызовах до создания окна; продолжается диагностика. ARM Sonoma crash из CI не воспроизведён на Intel. Доказательства сохранены в `/tmp/prebuilt-ui-fix-results/` (включая `monterey-final-evidence.tar.gz`, `sonoma-native-runtime-evidence.tar.gz`). +- **Дополнительная UI-квалификация 2026-10-22:** Sonoma Intel/Xcode16.2 выполнила также AppStoreSandboxUITests2/2 с изолированным временным keychain; исходные default/search lists восстановлены в GUI и SSH audit sessions. Один лишь SSH unlock не гарантирует доступа GUI app: запуск App Store блокировался в Keychain до native GUI unlock/selection. Native SDK13 контроль Monterey нашёл Customize на новой вкладке (XCUITest assertion1/1); sandbox-класс прошёл NTP после замены element timeout5s на navigation timeout, но затем General Settings перестала отвечать. Sample: `NSPathControlView.updateNSView → NSPathCell.setURL → FileProvider/CloudDocsFileProvider → fp_isInSyncableRootFast → open`; это не доказанный бесконечный SwiftUI layout loop и не failure загрузки XCTest bundle. В VM виден запрос доступа Review к Downloads. Проверка разрешения folder privacy ожидает человеческого согласия: автоматический approval review отклонил выдачу доступа. Cancel/Deny и перезапуск устаревших UI helpers не закрыли квалификационный gap. Monterey смешанный прогон1 выполненный метод+1 OS skip завершился успешно; all-skipped по-прежнему ошибка. Новый Sequoia lease `lease-e22b152c5d0d4c9bc8540b3bdf9a90e7` готов, macOS15.8.1/Xcode26.3, но Automation Mode disabled/requires user authentication; native UI пока не запускался, изменение unattended-политики также ждёт явного разрешения после auto-review отказа. Loader-only631UI+5Perf+5Sync успешен. На GitHub ARM14 и15 точечные классы прошли в обоих режимах на1066967a82; финальные полные UI/Sync/Performance на7cbda2bc49 ещё выполняются. Дополнительные receipts: `/tmp/prebuilt-ui-fix-results/sonoma-appstore-evidence.tar.gz`, `monterey-additional-evidence.tar.gz`. - Remote shell zsh: нет `timeout`, `$var` не разбивается на слова — при автоматизации писать скрипты файлами. - Запуск из `xcodebuild test-without-building -xctestrun` по скопированным `Products` (macOS 27) работает; `Scheme`-pre-actions (`clean-app.sh`, `tests-server`) при этом **не** выполняются. - Повторы `vm_acquire` — с новым idempotency key, если меняется ключ SSH/шаблон; тот же ключ и body только для настоящего retry. - Клиентская сторона (не virfield): auto-mode classifier заблокировал `vm_acquire` для шаблона, который пользователь не называл; разрешение нужно давать явно. +## Диагностика DDG Review Keychain, 2026-10-23 + +**Классификация: проблема дополнительного test fixture; дефект подписи приложения или Virfield не подтверждён.** Подпись Review `Developer ID Application: Duck Duck Go, Inc. (HKE973VLUW)` прошла `codesign --verify --deep --strict` в VM. Речь о runtime-ключе шифрования данных браузера, а не о приватном ключе сертификата сборки. Подпись идентифицирует приложение для проверки доступа, но не разблокирует keychain; см. [Apple: Access Control Lists](https://developer.apple.com/documentation/security/access-control-lists). + +В lease `lease-f7b5ad7345b33883e6ef9062062125f7` клиент тестов сам создал дополнительный `workspace/fire-regression/keychain-fixture/review.keychain-db`, записал синтетический AES fixture и затем изменил default/search list VM на этот keychain. Это не keychain, подготовленный Virfield. Через System Events прочитан точный запрос: “DuckDuckGo Review wants to use the ‘review’ keychain. Please enter the keychain password.” Успешные CLI unlock и отдельный GUI helper (status 0) не доказали доступ target app. Точная причина повторного запроса разблокировки этого fixture не установлена; необходимость менять ACL `login` из этого запроса не следует. ACL существующего ключа в `login` не изменялся. + +Контроль на свежем lease `lease-190e6d0deae478e311732c28e898f902`, тот же golden `image-f11d9a1ee2991eada08c4f734c33df6d`, тот же подписанный app (Signed Time 11:17:24 UTC), **без дополнительного keychain и без изменения ACL**: до запуска отсутствовали обе версии browser encryption item; приложение само создало v2 item в штатном `login` (metadata cdat 11:52:35 UTC). SecurityAgent отсутствовал. XCTest после обычной подготовки UI fixture запустил Review, загрузил тестовую страницу и дошёл до Fire-диалога без запроса Keychain. + +Первые два контрольных запуска не являются успешными UI-прогонами: первый остановился в LetsMove `PFMoveToApplicationsFolderIfNecessary → NSAlert.runModal` (подтверждено sample), второй — на отсутствующем loopback tests-server. После установки существующей в репозитории настройки `moveToApplicationsFolderAlertSuppress` и запуска штатного tests-server третий дошёл до Fire-диалога. Его итоговый xcresult не экспортирован: lease удалён через journal cleanup в 11:57:26 UTC при expires_at 12:50:44 UTC. Предыдущий lease аналогично удалён в 11:48:21 UTC при expires_at 12:16:36 UTC. Из исследующего чата release для этих двух VM не вызывался; инициатор cleanup по доступному журналу не установлен. Полный зелёный UI-прогон этим контролем **не подтверждён**. + +Практическое правило для DDG Review: сначала использовать штатные default/search list и дать приложению создать собственный тестовый runtime-ключ; не добавлять отдельный keychain по предположению о неисправной подписи. При необходимости отдельного fixture проверять доступ именно target app в контексте его запуска. Готовность native GUI/SSH probe Virfield и наличие подписи сами по себе не квалифицируют произвольный дополнительный keychain. + +## Прерывание Fire UI из-за внешнего диска, 2026-10-23 + +На macOS 12.7.6 / Xcode 13.4.1 / SIP off полный прогон 13 Fire + 2 Downloads завершился 15/15 без failures/skips; xcresult экспортирован и SHA-256 проверен (`391574a0ecdb749b51ed2ec00f21a41095d01ad1c05ae64f59c2251590607ab1`). Использовался подписанный Developer ID Review, штатный login keychain, без дополнительного keychain и изменения ACL. + +Повтор на финальном UI fixture-коммите `895a1208cb` в lease `lease-7c4845485566c07b3c81d4c7ce5cd29f` прервался по инфраструктурной причине: в 14:49:40 UTC Virfield зарегистрировал `vm_drift`. Host unified log подтверждает unmount Samsung970EVO (`disk7s1`) в 14:49:42 UTC и повторное монтирование после unclean unmount в 14:50:30 UTC. Tart PID 89926 оставался жив, но `lsof` показывал revoked дескрипторы и недоступный cwd; SSH перестал отвечать. Это потеря backing storage запущенной VM, а не установленный дефект подписи/keychain. Причина самого отключения внешнего диска не установлена. После `cleanup succeeded` и состояния `released` обнаружен оставшийся Tart PID 89926 с точными аргументами этого удалённого worker; каталога VM уже не было. Штатный `resolve` отказал (`only leases needing attention can be resolved`). После проверки released lease, отсутствующего каталога и точного PID/argv выполнен SIGTERM только этого orphan-процесса, его завершение подтверждено. Следовательно, успешный cleanup после потери backing volume в этом случае не подтвердил завершение процесса Tart. Штатная revalidation не прошла проверку running VM + SSH; lease освобождён через `vm_release`, cleanup `job-ebcdd093838c10a1b4a1d26b417e38d3` succeeded. Прерванный повтор не считается результатом тестов. + +## Зависание AppleParavirtGPU в Monterey UI worker, 2026-10-23 + +В свежем SIP-off Monterey lease `lease-50a13570aea1fc5518cd0bad61f42e3d` на штатном keychain первые 6 UI-методов прошли. Следующий остановился в XCTest `click` при подготовке клика в исходной очистке, до проверяемого закрытия Fire Window. `sample` runner: `_XCUIMatchingPointInFrameUsingContext`; `sample testmanagerd`: `_XCT_requestScreenshot → ImageIO HEIF → VideoToolbox → Metal → AppleParavirtGPUMetalIOGPUFamily`. Главный поток Review ожидал обычное событие в run loop; AppKit NSPersistentUI snapshot queue также ждала IOSurface. Guest kernel log: `Trying to restart GPU (Undefined)...` и `stalling for detach from AppleParavirtGPU`. Повторного unmount внешнего диска в этот период не зарегистрировано. + +Это подтверждённая остановка графического стека VM; причина его зависания и необходимость исправления в Virfield пока не установлены. Нельзя считать её дефектом подписи/keychain или успешным тестом. Собраны три sample, kernel log, частичный xcresult; owned xcodebuild остановлен SIGINT. Архив SHA-256: `2355f674562533ead70b8f8e68cf5cfea1403dd00f8d9e3b863dd8c36a8047e0`. Все оставшиеся методы завершились успешно в двух коротких прогонах на свежих worker: `final895-a` 5/5 и `final895-c` 4/4, zero failures/skips; ранее в прерванном запуске прошли 6 других методов. Проверена уникальная совокупность всех 13 текущих Fire + 2 Downloads на коммите `895a1208cb` и одинаковый SHA-256 адаптированного под SDK13.4 runner (`2302bd20941f9229eb870dd3f5a48571e7a531ac21fe61bea03a79835337f8cc`). Assertions/timeouts не ослаблялись, анимация и GPU не отключались. Прерванный xcresult сохранён как Failed с двумя cancellation errors; его отменённый метод повторно прошёл в `final895-a`. Архивы результатов экспортированы и сверены: A `913b7e9b8be10e47df616c6d1be18698a90d140bc20f9857f933f090a2d44614`, C `da7b0a3d01b4026e2ea745e6a0a58bf813b99c52d47d7fb7cc41bbaabe5d2427`. + +## Текущая работа 2026-10-23: UI readiness + +Локальный `main` содержит коммиты `fd980dd`, `c7d75e7`, `1126997`, `6203177`, `5f605d8`, `7b5c124`, `4e913d5`, `5800007`; рабочее дерево чистое. Полный `make check` с race-тестами прошёл. Кэш Xcode на Mac очищен с 41 до 19 ГБ: распакованные копии удалены после сверки XIP, четыре XIP сохранены; код повторяет очистку. Для новых Xcode-образов и disposable workers действуют настройка unattended Automation Mode и native XCUITest gate 1/1; установленный daemon проверен на свежем Monterey образе и аренде. Остальные физические node-хосты требуют отдельного повторного сквозного прогона. + +Cold boot двух node VM на htpc выявил ошибки прежней установленной версии: Sonoma `lease-1e935304d66b68d41c7850a89f79d0ae` получил SSH примерно через 17 минут, Finder стабилизировался позже двухминутного desktop timeout; Sequoia `lease-1140ca109abe50cf3c095ae5c24a496b` дал временную ошибку чтения модели. Код увеличил бюджет node-lease до 45 минут, desktop до пяти минут и повторяет временно неудавшееся чтение модели. Обе VM требовали аутентификации Automation Mode. Sequoia lease удалён после подтверждения владельца; прежние временные leases позднее освобождены. Повторный прогон на этих node-хостах ещё не зафиксирован. + +Production 2026-10-23 обновлён clean-сборкой коммита `5800007`, подписанной доступным Apple Development сертификатом с прежним Team ID `E3R68ED529`; backup не создавался. Демон перезапущен и обслуживает действующие аренды. Старые временные leases освобождены. Full Disk Access после переподписи и свежие прогоны на всех физических node-хостах ещё требуют отдельной приёмки; Screen Recording/Downloads TCC конкретных приложений требует системного согласия. + +**SIP-off исправление внедрено, 2026-10-23:** коммит `4e913d5` делает SIP-disabled обязательным для новых образов и аренд, блокирует SIP-on источники и проверяет фактический `csrutil status` клона до `ready`. Для Monterey 12.7.6/21H1320 подготовлена чистая SIP-off Tart-основа, опубликована в приватном GHCR и закреплена digest `sha256:3733c94473dd603e048b64000db273a63fd0c8e5520d674cede1be9dad78cfee` в production config. Встроенный `image-create` собрал golden `monterey-xcode-13.4.1-sip-off` с Xcode 13.4.1/13F100; квалификация после reboot подтвердила SIP off, unattended Automation Mode и native XCUITest 1/1. Свежая арендованная VM `lease-b88068aa62098d8c02a659f2ab5593f7` повторила `csrutil status: disabled`, `DOES NOT REQUIRE user authentication`, Xcode 13.4.1 и XCUITest 1/1; её cleanup succeeded. Старый SIP-on golden удалён через `image-delete`, запись старого шаблона удалена из production config. Временные импорт и клонированные VM очищены. На htpc свежие Sonoma 14.8.9/Xcode 16.2 и Sequoia 15.8.1/Xcode 26.3 leases прошли SIP-off, unattended Automation Mode и XCUITest 1/1; обе проверочные аренды удалены. mbp2014 Monterey — физический MacBookPro11,1 (`kern.hv_vmm_present=0`), а не VM: SIP показывает custom configuration, консоль была заблокирована и UI smoke завершился `Running Background` (1 failure). Его `ready` подтверждает SSH, не UI; проверочная аренда удалена. Три остальных mbp2014 шаблона помечены `artifact_missing`. Остальные VM/хосты сквозным прогоном ещё не охвачены. + +## Цель: самостоятельный инстанс на поддерживаемом оборудовании + +**Статус 2026-10-23: не реализовано и не принято.** Каждый разработчик устанавливает **свой** Virfield со своими config, state, token, образами и MCP. Общий командный сервер, общий operator token и RBAC между разработчиками не входят в эту цель. «Одна кнопка» означает единый установщик или мастер, который определяет возможности машины, выполняет доступные шаги и показывает только неизбежные системные подтверждения. Не обещать запуск на оборудовании, где macOS VM технически недоступна; показывать конкретное ограничение до установки. + +### Проверенные пробелы + +1. README сейчас требует вручную установить Go/Python/Tart/Packer, клонировать и собрать проект, выполнить init, редактировать config.json, копировать бинарники, запускать daemon и отдельно регистрировать MCP. init не получает готовый образ. Это не самостоятельная установка в несколько действий. +2. Linux node VM требует вручную установить qcow2 bootstrap и OpenCore, настроить SSH, Podman/KVM/TUN, admin/sudo и профили. Автоматический путь от чистого поддерживаемого Linux-хоста до готового golden отсутствует. +3. Клиенту без virfield CLI приходится вручную создавать SSH-ключ, pinned known_hosts и config. Локальная установка должна создавать и хранить их сама; приватный ключ остаётся на машине владельца. +4. В продуктовой справке и docs использовалось собственное имя узла htpc (удалено из checkout 2026-10-23; выпуск ещё не собран). Runtime не проверял строку htpc, но node startup deadline 45 минут и desktop deadline 5 минут выбраны по замеру одного нашего cold boot. Кроме того, стандартные VM-рецепты и init предполагают гостевой admin с домашним каталогом /Users/admin. Это контракт конкретного профиля образа, а не путь аккаунта владельца на хосте; мастер должен выбирать совместимый профиль и проверять его, а бюджеты и требования к гостю не выводить из нашего оборудования. +5. Monterey на установленном clean release `5800007` проверен через встроенный image-create, квалификацию образа и свежую аренду с native XCUITest 1/1. Свежие Sonoma/Sequoia Intel leases на htpc прошли UI readiness и XCUITest 1/1. Для остальных конфигураций и независимой установки нужен сквозной прогон и воспроизводимый релизный пакет. +6. SIP-off критерий для Monterey закрыт: новый digest-pinned источник, golden и fresh lease проверены после reboot, старый SIP-on golden и шаблон удалены. Общее требование «SIP отключён на каждой VM» закреплено в коде и проверено на свежих Tart Monterey и node VM Sonoma/Sequoia; остальные доступные VM шаблоны ещё требуют сквозной проверки. Физический mbp2014 не является VM. Screen Recording/Accessibility/TCC конкретных workload отдельно не подтверждены; SIP-off сам по себе не доказывает их доступность. +7. Web UI, HTTP API и MCP имеют разные наборы операций. Для одного собственного инстанса допустим owner token; ранее предложенный общий командный RBAC не является блокером этой цели. Требуется полный поддерживаемый локальный цикл через web UI и MCP, включая ясную диагностику и безопасные повторы. + +### Порядок исправления и критерии + +1. **Матрица поддержки и preflight.** Явно описать поддерживаемые хосты и провайдеры; определить OS/архитектуру, гипервизор, виртуализацию, свободные CPU/RAM/диск, доступ к storage, Xcode/Apple prerequisites и права. Возвращать причину и действие для каждого отказа. Все пути, имена узлов, порты и бюджеты брать из выбора пользователя или обнаруженного окружения, не из нашего deployment. +2. **Пакет и мастер установки.** Подписанный проверяемый пакет без обязательной сборки исходников. Мастер создаёт отдельные private config/state/token, предлагает storage, получает/проверяет pinned base, регистрирует собственный сервис и MCP в выбранном клиенте; шаги идемпотентны и дают rollback. Для node backend автоматизировать поддерживаемый bootstrap или до выбора явно сообщать, что он пока не поддерживает one-click. +3. **Параметризовать готовность и приватность.** Вместо одного фиксированного 45/5-минутного бюджета использовать профиль провайдера и конфигурируемые пределы с безопасными default, общей deadline и фазовой диагностикой. Обязательный gate для каждого нового образа и клона: SIP-off; SIP-on образ нельзя публиковать или выдавать в lease. Существующие SIP-on goldens пересоздать из совместимого SIP-off источника и подтвердить после reboot; активные VM не менять на месте. Все Xcode-образы и рабочие клоны подтверждать native XCUITest с >=1 выполненным тестом до ready; проверить на быстрых и медленных хостах. Отдельно объявлять и проверять нужные workload capabilities (например Screen Recording, Accessibility, доступ к защищённым папкам) внутри создания образа или подготовки lease. Если требуемое разрешение не получено, lease не должен объявляться готовым к такому workload. Пользовательский тестовый скрипт не должен настраивать TCC/SIP/Automation Mode. +4. **Одинаковый продуктовый цикл в UI и MCP.** Создание образа, Apple sign-in при необходимости, acquire, локальный SSH identity/config, выполнение workload через SSH, экспорт, renew/recovery и release доступны без ручного JSON, known_hosts и работы с каталогом daemon. Операции опираются на один API и одинаковые durable job/idempotency semantics. +5. **Приёмка независимого пользователя.** На чистых поддерживаемых Apple Silicon Mac и Linux/KVM-хосте, с пустыми config/state и без наших путей/образов, пройти мастер → подключить UI/MCP → создать golden → получить VM → выполнить build и XCUITest → экспортировать результат → release. Повторить после restart, медленного cold boot, потери сети, нехватки диска и истечения Apple session. Выпускать только clean signed package с hashes и протоколом этих прогонов. + ## Ограничения и дальнейший план Следующие задачи не входят в принятую установленную VM/MCP/Broker-приёмку и не объявляются реализованными: @@ -72,7 +131,41 @@ macOS make check/build/Staticcheck прошли; настоящий read-only vo - Реализовать signed node-agent, WoL relay/power intents, grants/connectors и physical-host/browser desktop при отдельном приоритете. - Развивать /api/v3 capability selection, Target/EnvironmentProfile/ConnectionGrant, short-lived access, VS Code extension и linked Console/Broker flows. - Расширить crash/host-loss/partition/full-disk matrix, metrics/alerts, signed promotion/revocation. +- [x] Изучить MDM для VM Virfield: способ подключения, требования, польза и решение (итог ниже, 2026-10-23). Broker reviewer исправление уже входит в запушенный Broker main `13c29ac`. Завершённые локальные и remote ветки Broker удалены после содержательной сверки, reviewer worktree сохранён detached. Архив исходного patch: [[personal/projects/archive/virfield-release-cleanup-2026-10-16/docs/acceptance/broker-reviewer-readonly]]. Детальные прежние планы сохранены в [[personal/projects/archive/virfield-release-cleanup-2026-10-16/docs/VIRFIELD-3-PLAN]] и архиве superpowers. Их старые pending-статусы не заменяют результаты выше. Автоматизация остаётся на паузе по указанию пользователя. + +## MDM для Virfield: исследование и решение (2026-10-23) + +**Решение: не включать MDM в текущий путь создания disposable VM.** Virfield не содержит MDM-сервера, enrollment profile или операции регистрации VM. Сейчас он применяет SIP/Automation Mode/TCC через guest scripts и проверяет результат на образе; native XCUITest даёт прямое доказательство готовности runner. MDM не исправляет сам по себе прежний пропуск Automation Mode. + +Подключение потребовало бы отдельного MDM-провайдера, HTTPS endpoint, сертификата APNs, identity certificate для каждого гостя, enrollment profile и регистрации каждой VM. Для Apple Business Manager/ADE нужны ещё организационные связи и назначение устройств; пригодность ADE именно для наших виртуальных Mac не подтверждена и должна проверяться на отдельном disposable образе. Клонирование уже зарегистрированного golden недопустимо как проектное допущение: enrollment и identity нужно создавать для каждого worker отдельно, а при release удалять запись и сертификаты на сервере. + +Польза возможна для долговечных физических Mac: централизованные настройки и аудит, управляемые приложения, отдельные PPPC разрешения (например Full Disk Access и PostEvent). Для краткоживущих VM цена — внешний сервис, сертификаты, сеть/APNs, регистрация и очистка каждого worker. Главное ограничение для UI-тестов: Apple не разрешает MDM-профилем заранее дать ScreenCapture; только запретить. На macOS 27 старый PPPC grant для Accessibility убран; новый declarative AppSettings предлагает пользователю запрос согласия, но не выдаёт доступ без него. Поэтому MDM не заменяет наши UI-readiness проверки, настройку Automation Mode и человеческое согласие там, где его требует macOS. + +Источники Apple: [MDM enrollment](https://developer.apple.com/documentation/devicemanagement/deploying-device-management-enrollment-profiles), [инфраструктура/APNs](https://support.apple.com/en-gb/guide/deployment/depceaf9c713/web), [PPPC и ScreenCapture](https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services-data.dictionary), [macOS 27 AppSettings](https://developer.apple.com/documentation/devicemanagement/appsettings). Проверка текущего кода: `internal/qualify/automation.go`, `internal/guestscripts/security.sh`, `internal/guestscripts/provision27.sh`, `internal/config/config.go`. + + +## Баг: ложная занятость VM при отключённом Tart storage (2026-10-26) + +**Причина подтверждена по коду и текущему статусу Virfield.** Когда все настроенные Tart storage недоступны (сейчас `samsung-tart` сообщает `volume_unmounted`), `internal/tart/client.go` подставляет `HostUsed = HostMax`, чтобы закрыть запуск. Контроллер затем считает это реальными неизвестными VM: `capacityOn` показывает два «дополнительных слота», а `resources()` выставляет `resource_unknown: An active VM is missing from inventory`. Это не две реальные VM и не нехватка CPU/RAM; это синтетическое значение, ошибочно использованное как счётчик. + +### Путь исправления + +- Оставлять `HostUsed` и `UsedVMs` счётчиками фактически наблюдаемых VM; не использовать их для кодирования запрета. +- Закрывать локальную capacity отдельно, когда ни один primary Tart storage недоступен, и показывать конкретный blocker, например `storage container samsung-tart unavailable: volume_unmounted`. +- Передавать тот же blocker в ошибку acquire, чтобы объяснение не говорило о занятых слотах, если слоты фактически свободны. +- Сохранить fail-closed поведение: недоступный storage не должен позволять запуск/сборку, а доступный другой primary storage не должен блокироваться без необходимости. +- Регрессии: все storage отключены → фактическое `used=0`, `available=0`, причина — storage; нет ложной ошибки про отсутствующую активную VM. Также проверить смешанный случай с одним доступным storage и обычный доступный Tart путь. + +### Текущее состояние + +В `~/Developer/virfield` создан незакоммиченный локальный черновик: удалена подстановка `HostUsed = HostMax`; storage blocker перенесён в расчёт capacity; сообщение отказа включает blockers; добавлены regression tests. Прошли `go test ./internal/tart ./internal/control ./internal/domain` и `git diff --check`. Полные проверки `make check`/build, deployment и production-проверка ещё не выполнены. Изменения не закоммичены и не запушены; локальная `main` уже содержит 10 коммитов сверх `origin/main`, поэтому перед push нужно проверить полный набор коммитов. + +### Приёмка после деплоя + +1. На действующем статусе при отключённом диске увидеть фактическое число VM, `available=0` и точную причину storage; ошибка «active VM is missing from inventory» отсутствует. +2. Убедиться, что lease/build не стартует через недоступное хранилище. +3. После монтирования диска дождаться свежего inventory и проверить доступность шаблонов и нормальное распределение слотов. diff --git a/personal/tech/docker-mac-disk-issues.md b/personal/tech/docker-mac-disk-issues.md index 46146211..dce415a3 100755 --- a/personal/tech/docker-mac-disk-issues.md +++ b/personal/tech/docker-mac-disk-issues.md @@ -1,5 +1,7 @@ # Docker на Mac — проблема с диском и сборкой образов +> **2026-10-21: действующие автоматические правила** — [[personal/tech/macos-voom-cleanup|Voom, Eagle и автоматическая очистка Mac]]. Там описаны расписания, сроки, защита данных и ограничения Docker. Ниже сохранены исторические разборы и ручные сценарии очистки. + _Последнее обновление: 2026-09-29_ > **2026-09-29: выполнена большая чистка, освобождено 41 ГБ** (57 ГБ → 16 ГБ, 61% → 17%). diff --git a/personal/tech/flaresolverr-taiga.md b/personal/tech/flaresolverr-taiga.md index 413d54b4..51216f02 100755 --- a/personal/tech/flaresolverr-taiga.md +++ b/personal/tech/flaresolverr-taiga.md @@ -159,7 +159,30 @@ ssh truenas_admin@mallexxx.duckdns.org \ ## Восстановление quarantine -Если Prowlarr показывает старый `IndexerStatus`, сначала сделать backup SQLite. Остановить только Prowlarr, удалить только записи `IndexerStatus`, проверить `PRAGMA quick_check`, запустить и выполнить реальный search. Не удалять `prowlarr.db` и не считать health единственным тестом. +При старом предупреждении сначала подтвердить реальный search, выполнить одиночный штатный тест соответствующего индексатора и обновить health. Для Lidarr этот путь проверен 2026-10-26: RuTracker test + `CheckHealth` сняли предупреждение без редактирования БД. При API-тестировании использовать действующие секреты, а не маскированные значения из GET. Ручная очистка `IndexerStatus` — резервный способ только при неработающем штатном восстановлении: backup SQLite, остановка только затронутого сервиса, изменение только нужного статуса, `PRAGMA quick_check`, запуск и реальный search. Не удалять всю БД и не считать health единственным тестом. + +## Повторный сбой 2026-10-26 UTC / 2026-10-27 Бишкек + +Проверено на Taiga; различать два независимых дефекта. + +1. **Фактические отказы поиска:** Prowlarr писал `SQLiteException Busy (5): database is locked`. У RuTracker ошибки зафиксированы в 16:08:13 UTC и 17:20:59 UTC, в том числе в `GetCookies → ProviderStatusRepository.FindByProviderId`, до обращения к трекеру. Аналогичные ошибки были у Kinozal, NoNaMeClub и RuTor. Позже такие же блокировки продолжались в Lidarr. Это не доказательство потери авторизации RuTracker. +2. **Ошибка watchdog:** старый скрипт искал `id="logged-in-username"` через grep в сыром JSON FlareSolverr. Кавычки HTML в JSON экранированы, поэтому рабочая авторизация ошибочно считалась потерянной и session пересоздавалась каждые пять минут. На одной живой сессии подтверждено: parsed HTML logged-in=true, старый grep=false. + +Исправлен `/mnt/RED_2TB/docker/arr/flaresolverr-entrypoint.sh`: сначала разбирается JSON, затем проверяется HTML. Повторный login выполняется при явно обнаруженной форме входа; ошибки транспорта, challenge и некорректный ответ считаются неопределённым состоянием и не уничтожают действующую сессию. Таймаут проверки согласован с `BROWSER_TIMEOUT=180000`: 180 секунд внутри FlareSolverr и 240 секунд у curl. Смена egress-IP по-прежнему вызывает восстановление. + +Backup исходного скрипта: +`/mnt/RED_2TB/docker/arr/flaresolverr-entrypoint.sh.before-json-auth-fix-20261026T175021Z`. +Перезапущен только FlareSolverr; SQLite вручную не редактировалась. + +Приёмка: +- синтаксис shell и пять проверок классификатора ответа прошли; +- после перезапуска и очередного пятиминутного цикла вход сохранился, HTTP 200, ложных refresh в логах нет; +- реальный поиск Prowlarr по RuTracker: HTTP 200, 50 результатов за 17 секунд; +- одиночный штатный тест RuTracker в Lidarr прошёл; +- после `CheckHealth` предупреждение о недоступных индексаторах исчезло; осталось только уведомление об обновлении Lidarr; +- поисковые проверки не запускали новые скачивания. + +Ограничение: причина локальных блокировок SQLite не устранена этим изменением. На NAS измерено `/proc/pressure/io`: some около 66–70%, full около 50–54%; ZFS pool ONLINE без ошибок, первый mirror заполнен почти полностью (1.79 TiB занято, 27 GiB свободно), новый mirror почти пуст. Это подтверждает высокое ожидание I/O и неравномерное размещение данных, но не доказывает единственную причину блокировки. Настройки ZFS, маршрутизация и мониторинг музыкальной библиотеки не менялись. ## Итоговый рабочий результат diff --git a/personal/tech/home-network-config.md b/personal/tech/home-network-config.md index 00f4fb60..ef4c0d66 100755 --- a/personal/tech/home-network-config.md +++ b/personal/tech/home-network-config.md @@ -1,7 +1,7 @@ --- title: home-network-config — skill notes created: '2026-09-23' -updated: '2026-09-23' +updated: '2026-10-30' type: tech namespace: personal tags: @@ -81,6 +81,14 @@ if ifconfig | grep -q 10.99.0.2; then nc 10.99.1.2 22; else nc kraken 22; fi - Kraken Samba: `map to guest = bad user` обязателен в `[global]` для guest-доступа. - USB-девайсы в docker: `/dev/serial/by-id/...` **НЕ работают**, только `/dev/ttyUSB`. +## Телевизор в Новосибирске — проверено 2026-10-30 + +- **Konka Q85, 85″, webOS 7.5.6**: текущий адрес `192.168.2.124`, сетевое имя `LGwebOSTV`, сеть OpenWrt `192.168.2.2`. +- Установлен Homebrew Channel 0.7.3, root проверен после перезагрузки. Выполнено обратимое отключение ACR, рекламы, диагностики, рекомендаций и голосовых/ThinQ-компонентов; защищённые системные службы сохранены. +- Подробный статус, ограничения проверок, доступ и откат: [[personal/tech/konka-q85-novosibirsk]]. +- Это отдельный ТВ; сведения о `192.168.1.70` / webOS 5.4.2 из [[family/how-to/htpc-webos-luna]] к нему не относятся. +- Прямой SSH по ключу к `root@192.168.2.2` в этой сессии проверен. SSH на самом ТВ не включали; для управления использовали WSS `3001` и Homebrew. + ## Связанное - [[family/how-to/rasputin-router]] — policy routing, `ip rule`, транзит между сегментами diff --git a/personal/tech/konka-q85-novosibirsk.md b/personal/tech/konka-q85-novosibirsk.md new file mode 100644 index 00000000..eddcbc4b --- /dev/null +++ b/personal/tech/konka-q85-novosibirsk.md @@ -0,0 +1,186 @@ +--- +title: 'Konka Q85 — Новосибирск: статус и изменения' +created: '2026-10-30' +updated: '2026-10-30' +type: tech +tags: + - tv + - konka + - webos + - novosibirsk + - homebrew + - privacy +related: + - '[[personal/tech/home-network-config]]' + - '[[family/how-to/htpc-webos-luna]]' + - '[[personal/tech/lampac-truenas]]' +--- +# Konka Q85 — Новосибирск: статус и изменения + +## Lampac установлен и проверен — 2026-10-30 + +Установлен `com.lampac.tv` версии1.0.0 через существующий Homebrew Channel с проверкой SHA256. Сервер и сохранённый `lampac_url`: `http://192.168.2.197:9118` (TrueNAS). Полная схема: [[personal/tech/lampac-truenas]]. + +Настройки клиента: **`proxy_tmdb=true`, `proxy_tmdb_auto=false`**. Автоопределение региона выключало прокси и ломало встроенные постеры, хотя Catalog уже был настроен. На сервере прокси отдельно включён для TMDB API/изображений, `posterApi` и `serverproxy.image`; глобальный прокси видео не включён. + +Подтверждены поиск «дюна» через клавиатуру без JavaScript-ошибок, 7 встроенных и28 собственных постеров (сломанных0), начало прямого воспроизведения тестового H.264640×360 с NAS (`readyState=4`, ошибок нет). Длительное воспроизведение/онлайн-раздача и звук на слух отдельно не подтверждены. Общая история на TrueNAS через API; репликации SQLite нет. + +При работе Lampac доступна CDP-цель9998: выбирать по URL с `com.lampac.tv`, ждать `appready`. Root-вызов Homebrew через `PalmServiceBridge` проверен также из контекста установленного Lampac; SSH/Telnet на ТВ ради этого не включали. Диагностические ответы не выводить поверх приложения. Временные HTTP-установщик на роутере18085 и SSH-перенаправления после работы удалены. Настройки root/privacy ниже не менялись. + +Состояние проверено **2026-10-30** во время настройки телевизора. Местоположение — Новосибирск, по указанию владельца. Это отдельный телевизор; не путать с ТВ `192.168.1.70` / webOS 5.4.2 из [[family/how-to/htpc-webos-luna]]. + +## Устройство и сеть + +- Konka, диагональ 85″ — со слов владельца; модель, сообщаемая самим ТВ: **Q85**. +- ПО: **04.56.00** — сообщено владельцем; API полной версии ПО отказал в доступе, поэтому это значение не выдаём за независимо прочитанное. +- webOS **7.5.6**, DVB — непосредственно прочитано с телевизора. +- Ядро: `5.4.96-115.28.1.27.mirima.1`, архитектура `aarch64`. +- Текущий адрес: **192.168.2.124**, имя DHCP: `LGwebOSTV`, MAC: `7c:64:6c:33:82:37`. Это обнаруженный адрес, закрепление DHCP не проверялось и не настраивалось. +- Шлюз OpenWrt: **192.168.2.2**, Xiaomi Redmi Router AX6S. SSH `root@192.168.2.2` по настроенному ключу проверен. +- WSS управления ТВ: `192.168.2.124:3001`. Выполнено сопряжение с подтверждением на экране. Соединение WS на 3000 сбрасывалось; для работы использовали WSS 3001. +- SSH на самом ТВ не включали. Проверенные до настройки порты 22, 9922 и 9991 были закрыты; доступ SSH к роутеру не означает наличия SSH на телевизоре. + +Связанный документ сети: [[personal/tech/home-network-config]]. + +## Root и Homebrew + +2026-10-30 выполнен jailbreak через **DangBro**. На ТВ подтверждено наличие `com.webos.app.dangbei-overlay`; код запускался в его контексте. Проверенный установщик и Homebrew Channel раздавались временно с роутера, без прошивки системных разделов. + +- Установлен **Homebrew Channel 0.7.3**, appId `org.webosbrew.hbchannel`. +- Прямой вызов `id` вернул `uid=0(root) gid=0(root)`; root повторно подтверждён после перезагрузок. +- Приложение LG Developer Mode `com.palmdts.devmode` удалено установщиком, поскольку конфликтует с сохранением root. Повторно устанавливать его без проверки совместимости не следует. +- Включившийся при установке Telnet без пароля отключён, закрытие порта 23 проверено. Сохранён флаг `/var/luna/preferences/webosbrew_telnet_disabled`. +- Временные серверы на роутере `:18085` остановлены; их каталоги, установщики и тестовые ролики на роутере удалены. Постоянной службы на роутере для этих изменений нет. + +## Найденное состояние до отключения + +- LivePlus был включён: `option.livePlus = "on"`, `eulaStatus.acrOnAllowed = true`. +- Были приняты голосовые согласия: `voiceAllowed = true`, `voice2Allowed = true`. +- `additionalDataAllowed` и `shoppingOnAllowed` были `true`. +- Реклама главного экрана и рекомендации уже были выключены: `general.homePromotion = "off"`, `other.contentRecommendation = "off"`. +- Согласия на персонализированную рекламу и удалённую диагностику уже были выключены. +- Работали рекламные, диагностические, рекомендательные и голосовые службы, в том числе `admanager`, `contentminer`, `rdxd`, `uploadd`, `remotelogger`, `service-logger`, `voiceinput`, `voiceconductor`, `nlpmanager`, `performer`. +- Установлены LivePlus/ACR-компоненты для HDMI, рекламные оболочки, Alexa, Google Assistant и Алиса. Само наличие пакета или процесса не доказывает запись разговоров либо отправку данных. +- Homebrew уже монтировал очереди `rdxd/uploadd` только для чтения; это ограничение диагностических очередей, а не полное отключение ACR. +- В отдельном снимке сети внешних TCP-соединений не было. Длительное наблюдение, расшифровка трафика и доказательство отсутствия периодических отправок не выполнялись. + +## Что отключено по согласованию владельца + +Отключены найденные компоненты следующих групп: + +- ACR и реклама: `acr2`, `admanager`, `adoverlay-service`. +- Диагностика и загрузка журналов: `rdxd`, `uploadd`, `remotelogger`, `service-logger`. +- Рекомендации и уведомления: `contentminer`, `nudge`, `sportsalarm`. +- Голос, Alexa и ThinQ/IoT: `voiceinput`, `voiceconductor`, `nlpmanager`, `performer`, `amazon-alexa-adapter`, `amazon-alexa-vsk`, `lg.thinqai.adapter`, `iot-client`, `mqtt-client`. +- `musicrecognition` предусмотрен списком отключений, но при проверке соответствующая программа не найдена; не считать её установленной или реально отключённой. + +LivePlus переведён в `off`. В существующем объекте согласий выключены ACR, голосовые, рекламные, дополнительные данные и shopping-согласия; общие условия, разрешение сети и непонятный по назначению `chpAllowed` сохранены. + +**Следствие:** голосовой поиск, Alexa и связанные ThinQ/IoT-функции больше не должны работать. Приложения Google Assistant/Алисы и другие системные оболочки не удалялись; отключён обслуживающий голосовой тракт. + +## Как устроены изменения + +Системные программы не стирались и не перезаписывались. Поверх выбранных исполняемых файлов, включая уже существовавшие копии в sandbox-каталогах, монтируется в памяти безвредный скрипт с `exit 0`. Выбранные systemd-службы останавливаются; оставшиеся целевые процессы завершаются. При итоговой проверке целевых процессов не было, ошибок подмен не было. + +Файлы на телевизоре: + +- Управление: `/var/lib/konka-privacy/control.py`. +- Исходные настройки и состояния служб: `/var/lib/konka-privacy/before.json`. +- Список временных подмен: `/var/lib/konka-privacy/mounts.json`. +- Автозапуск Homebrew: `/var/lib/webosbrew/init.d/70-konka-privacy`. +- Журнал применения после загрузки: `/tmp/konka-privacy-boot.log`. +- Флаг запрета применения: `/var/lib/konka-privacy/disabled`. +- Отметка успешной итоговой проверки: `/var/lib/konka-privacy/verified`. + +**Особенность:** во время проверки webOS восстановила часть согласий и LivePlus при перезагрузке. Поэтому настройки повторно выключаются в автозапуске вместе со службами. Обработчик ждёт 35 секунд после вызова из загрузочного механизма Homebrew; отключения не действуют с самой первой секунды включения. На проверенной загрузке успешный результат получен примерно на 83-й секунде uptime. + +При первичной проверке был предусмотрен автоматический откат, если итоговая проверка не завершится. Сейчас отметка `verified` уже существует: **не рассчитывать на автоматический откат будущих изменений**, использовать команды ниже. + +## Что сохранено + +`sdx`, `ss.gateway`, SAM, Luna Hub, настройки, системный интерфейс, сеть, HDMI, видеовывод и звуковые службы не включены в список отключений. Приложения не удалялись, домашний экран не заменялся, системные плитки не скрывались, перезапуск SAM для таких изменений не выполнялся. + +Системные разделы, загрузчик, ядро и прошивка не перезаписывались. Блокировку обновлений и новые DNS/hosts-списки эта настройка не добавляла. + +## Проверка результата + +После повторной перезагрузки подтверждены: + +- root: `uid = 0`; +- LivePlus: `off`; выбранные согласия выключены; +- отсутствие работающих целевых процессов; +- активное состояние SAM, Luna Hub, surface-manager, umediaserver, videooutputd, audiooutputd и audiod; +- наличие процессов `sdx`, `SettingsService`, `connmand`; +- наличие обработчика автозагрузки и отметки успешной проверки; +- воспроизведение отдельного тестового H.264-видео: время 12.082 секунды, 640×360, `readyState = 4`, ошибок нет; видеовывод MAIN подключён и не muted, около 24 кадров/с. + +Один предварительный тест с коротким зацикленным роликом получил ошибку декодирования; этот результат не считали подтверждением нормального воспроизведения. После завершения автоприменения выполнены отдельные успешные проверки, включая более длинный ролик без цикла. + +Физическое звучание динамиков, сигнал внешнего HDMI-устройства, все стриминговые приложения и цикл сна/пробуждения отдельно не проверялись. Активность звуковых служб не равна проверке звука на слух. + +До изменений уже были failed-unit: `accountmanager`, `bugreportd`, `crashreportd`, `dropbear`, `kadpd`, `kdrivers`. Итоговый список совпал с исходным; не приписывать эти состояния выполненному отключению. + +## Откат и диагностика + +Команды выполнять **в root-контексте на телевизоре**, а не на OpenWrt. + +Проверка: + +```sh +python3 /var/lib/konka-privacy/control.py status +cat /tmp/konka-privacy-boot.log +``` + +Полный откат с восстановлением исходных настроек и удалением автозапуска отключений: + +```sh +python3 /var/lib/konka-privacy/control.py restore +``` + +После успешного отката полностью перезагрузить ТВ. Режим standby не заменяет полную перезагрузку. + +Для запрета повторного применения при следующей полной загрузке: + +```sh +touch /var/lib/konka-privacy/disabled +``` + +Затем полностью перезагрузить ТВ. Этот способ снимает временные подмены за счёт перезагрузки, но **сам по себе не восстанавливает прежние значения согласий**; для полного возврата нужен `restore`. + +Не удалять `before.json` перед откатом. + +## Доступ для дальнейшего обслуживания + +Проверенный путь этой сессии: SSH к OpenWrt → временная локальная раздача страницы → WSS-сопряжение с ТВ → запуск страницы через `com.webos.app.dangbei-overlay` → вызов Homebrew из `PalmServiceBridge`: + +```js +var bridge = new PalmServiceBridge(); +bridge.onservicecallback = function (result) { /* обработать JSON-ответ */ }; +bridge.call( + "luna://org.webosbrew.hbchannel.service/exec", + JSON.stringify({command: "python3 /var/lib/konka-privacy/control.py status"}) +); +``` + +Это работает в подходящем приложении на ТВ, а не в обычном браузере компьютера. Прямой запрос `ssap://org.webosbrew.hbchannel.service/exec` через WSS вернул 404; не считать его рабочим способом root-доступа. Обычное открытие страницы установщика на компьютере тоже не выполняет код в контексте ТВ. + +Для `luna-send` на **этом** телевизоре проверен явный appId: + +```sh +luna-send -w 5000 -n 1 -f -a org.webosbrew.hbchannel.service \ + luna://com.webos.settingsservice/getSystemSettings \ + '{"category":"option","keys":["livePlus"]}' +``` + +Не переносить сюда без проверки рецепты старого ТВ с webOS 5.4.2. Секреты сопряжения и приватные SSH-ключи в заметку не копировать. Временную раздачу после обслуживания останавливать. + +## Источники использованных решений + +- [DangBro](https://github.com/azoffshowy/dangbro) — выполненный способ root. +- [Homebrew Channel](https://github.com/webosbrew/webos-homebrew-channel). +- [lg-webos-ad-killer](https://github.com/nurikk/lg-webos-ad-killer) — примеры обратимого отключения; готовый полный скрипт на ТВ не устанавливался. +- [CleanYourTV для webOS: debloat](https://lasharor.github.io/cleanyourtv-WebOS/debloat/) — группы служб и временные подмены. +- [RootMyTV: системные разделы не перезаписывать](https://rootmy.tv/warning). +- [Glasshouse issue 366](https://github.com/rorygallagher2024/glasshouse/issues/366) — проблемы изображения/звука при перезапуске SAM. + +Публичного проверенного списка отключений именно для Konka Q85 / webOS 7.5.6 в ходе поиска не найдено. Основание текущего статуса — фактические проверки этого телевизора, а не обещание совместимости чужого полного набора изменений. diff --git a/personal/tech/lampac-deployment.md b/personal/tech/lampac-deployment.md index 6f42db8c..2cac7307 100755 --- a/personal/tech/lampac-deployment.md +++ b/personal/tech/lampac-deployment.md @@ -1,7 +1,7 @@ --- title: Lampac — развёртывание и эксплуатация (Кра́кен) created: '2026-09-22' -updated: '2026-09-25' +updated: '2026-11-01' type: tech namespace: personal tags: @@ -21,10 +21,35 @@ related: - '[[personal/tech/multistream-single-flow-limit]]' - '[[family/how-to/kraken-network]]' - '[[family/how-to/tv-luna-send]]' + - '[[personal/tech/lampac-truenas]]' --- # Lampac — развёртывание и эксплуатация (Кра́кен) +## Kraken: дисковый кэш TorrServer — проверено 2026-11-01 + +На Kraken стандартный RAM-кэш96 МиБ заменён на **2 ГиБ кэша на диске**: `CacheSize=2147483648`, `UseDisk=true`, `TorrentsSavePath=/lampac/cache/torrserver`, `RemoveCacheOnDrop=true`. + +Хост-каталог: `/srv/dev-disk-by-uuid-6194539b-03e0-4f33-986b-4da09834e82f/lampac/cache/torrserver`; используется существующий постоянный cache bind mount. Настройка сохраняется в `data/ts/settings.json`. Перед переключением было около1,2 ТБ свободного места; активных раздач не было. Старые настройки и список торрентов сохранены с mode0600 в `backups/kraken-disk-cache-20261101/`. + +Применено через авторизованный локальный API TorrServer внутри контейнера; пароль из `data/ts/accs.db` не публикуется. Контейнер не пересоздавался, явный Docker-лимит памяти Kraken не добавлялся и не менялся. Это2 ГиБ **дискового**, а не зарезервированного RAM-кэша; рабочая память и освобождаемый файловый кэш ОС остаются. + +Проверены API и сохранённый конфиг. Отдельный временный Ubuntu-торрент подтвердил реальную запись8 файлов фрагментов на диск (около1,98 МБ) и чтение1 МиБ потока HTTP Range. Тестовая раздача удалена после проверки. Видео не переводилось на сетевой прокси. + +Штатная смена настроек MatriX.135 сбрасывает активные торренты и заново подключает движок; если фильм открыт при будущей смене режима, может потребоваться открыть его повторно. Описание общей схемы — [[personal/tech/lampac-truenas]], инструкция — [lampac-scripts/deploy/README.md](https://git.mallexxx.duckdns.org/git_admin/lampac-scripts/src/branch/main/deploy/README.md). + +## Актуальная схема на 2026-10-30: Kraken + TrueNAS + +Рабочая схема, адреса, настройки и восстановление — [[personal/tech/lampac-truenas]]. Официальный Lampac1.87.3: Kraken arm64, TrueNAS amd64; наш репозиторий — аддон, не форк сервера. Репозиторий и инструкция: [lampac-scripts/deploy](https://git.mallexxx.duckdns.org/git_admin/lampac-scripts/src/branch/main/deploy/README.md). + +На TrueNAS приложение работает в `/mnt/RED_2TB/docker/lampac`, LAN `http://192.168.2.197:9118`; клиент Konka Q85 в Новосибирске (`192.168.2.124`) установлен и проверен. Прокси только метаданных/постеров: отдельный `lampac-egress` через Kraken; глобальный прокси видео не включён. На Konka обязательно `proxy_tmdb=true`, `proxy_tmdb_auto=false`; настроены также `tmdb.proxyapi/proxyimg`, `posterApi`, `serverproxy.image`. + +Общая TimeCode теперь на TrueNAS, Kraken обращается через HTTPS API с отдельным `history_key` и тем же `uid`. Синхронизации SQLite нет; 9 записей сохранены. Генератор каталога/писатель Obsidian один, на Kraken; на NAS передаются только готовые JSON. Бэкап NAS учитывает uid950/uid1000: БД/WAL/SHM mode0664, group1000. Запись после бэкапа проверена. + +Ошибка поиска `title.toLowerCase()` исправлена в Catalog-аддоне: discovery каждого источника привязан к имени каталога, отдельная повторная регистрация поиска убрана. Проверены реальный поиск на ТВ, встроенные и собственные постеры, прямое тестовое воспроизведение. Подробные границы проверки — в новой заметке. + +Ниже сохранена история настройки Kraken за сентябрь; прежние указания читать активную историю из локальной SQLite Kraken заменены схемой выше. + > Технический справочник. Развёрнуто 2026-09-22. План и статус шагов — [[personal/plans/lampac-kraken]]. ## 🔴 2026-09-25: деплой `plugin.js` модуля Catalog — контракт и подводные камни diff --git a/personal/tech/lampac-truenas.md b/personal/tech/lampac-truenas.md new file mode 100644 index 00000000..a9ba8dbb --- /dev/null +++ b/personal/tech/lampac-truenas.md @@ -0,0 +1,112 @@ +--- +title: Lampac на TrueNAS и Konka Q85 — рабочая схема +created: '2026-10-30' +updated: '2026-11-01' +type: tech +namespace: personal +tags: + - lampac + - truenas + - konka + - webos + - timecode +related: + - '[[personal/tech/lampac-deployment]]' + - '[[personal/tech/konka-q85-novosibirsk]]' + - '[[family/how-to/truenas-infrastructure]]' +--- +# Lampac на TrueNAS и Konka Q85 — рабочая схема + +## Kraken: дисковый кэш TorrServer — проверено 2026-11-01 + +На Kraken стандартный RAM-кэш96 МиБ заменён на **2 ГиБ кэша на диске**: `CacheSize=2147483648`, `UseDisk=true`, `TorrentsSavePath=/lampac/cache/torrserver`, `RemoveCacheOnDrop=true`. + +Хост-каталог: `/srv/dev-disk-by-uuid-6194539b-03e0-4f33-986b-4da09834e82f/lampac/cache/torrserver`; используется существующий постоянный cache bind mount. Настройка сохраняется в `data/ts/settings.json`. Перед переключением было около1,2 ТБ свободного места; активных раздач не было. Старые настройки и список торрентов сохранены с mode0600 в `backups/kraken-disk-cache-20261101/`. + +Применено через авторизованный локальный API TorrServer внутри контейнера; пароль из `data/ts/accs.db` не публикуется. Контейнер не пересоздавался, явный Docker-лимит памяти Kraken не добавлялся и не менялся. Это2 ГиБ **дискового**, а не зарезервированного RAM-кэша; рабочая память и освобождаемый файловый кэш ОС остаются. + +Проверены API и сохранённый конфиг. Отдельный временный Ubuntu-торрент подтвердил реальную запись8 файлов фрагментов на диск (около1,98 МБ) и чтение1 МиБ потока HTTP Range. Тестовая раздача удалена после проверки. Видео не переводилось на сетевой прокси. + +Штатная смена настроек MatriX.135 сбрасывает активные торренты и заново подключает движок; если фильм открыт при будущей смене режима, может потребоваться открыть его повторно. Описание общей схемы — [[personal/tech/lampac-truenas]], инструкция — [lampac-scripts/deploy/README.md](https://git.mallexxx.duckdns.org/git_admin/lampac-scripts/src/branch/main/deploy/README.md). + +Проверено 2026-10-30. Lampac развёрнут на TrueNAS, клиент установлен на Konka Q85 в Новосибирске. Это аддон к официальному серверу, не форк Lampac. + +## Дисковый кэш TorrServer — обновление 2026-10-31 + +**Текущая настройка: 2 ГиБ кэша на диске**, не в RAM: `UseDisk=true`, `CacheSize=2147483648`, `TorrentsSavePath=/lampac/cache/torrserver`, `RemoveCacheOnDrop=true`. На хосте это `/mnt/RED_2TB/docker/lampac/cache/torrserver`, существующий постоянный cache bind mount. Лимит памяти контейнера возвращён с4 к **2 ГиБ**. После перехода фактическое потребление контейнера было около1 ГиБ. ОС/ZFS могут использовать освобождаемую память для файлового кэша; это не отдельная обязательная RAM-буферизация2 ГиБ в TorrServer. + +Первоначально оставлен стандартный RAM-кэш96 МиБ, затем просьба увеличить его была неверно интерпретирована как2 ГиБ RAM. Владелец уточнил, что не хотел занимать столько оперативной памяти. После проверки исходников MatriX.135 включён штатный дисковый backend; ограничения, мешающего писать кэш на диск, нет. + +Применение через оболочку Lampac `/ts/settings` вернуло500 и не изменило настройку. Повтор через авторизованный **локальный** API TorrServer `127.0.0.1:9085` внутри контейнера с приватными данными `data/ts/accs.db` сработал. Порт9085 наружу не открывался. Штатный SetSettings сбрасывает активные торренты/подключает движок заново, поэтому воспроизведение при смене режима может потребовать повторного открытия. Не обещать применение без сброса активного буфера. + +Проверено фактически: `UseDisk=true` в API и сохранённом `data/ts/settings.json`; появились4 файла фрагментов,16 МиБ на диске; HTTP Range-запрос видеопотока получил206 и прочитал1 МиБ. Контейнер не пересоздавался. Прежние настройки: `backups/torrserver-before-disk-cache.json`, Compose: `backups/compose-before-disk-cache.yml`. + +Фильм «Красота по-американски» — Remux31,37 ГиБ, средний битрейт36,9 Мбит/с. Замеры скачивания25–43 Мбит/с, запас в буфере ТВ4,8–6с. Поток шёл напрямую с `192.168.2.197:9118/ts/stream/…`; прокси обслуживал TMDB. Увеличение кэша сглаживает временные просадки после заполнения, но не устраняет постоянную нехватку скорости раздачи. Файлы кэша вручную при активном потоке не удалять. + +## Серверы и исходники + +- TrueNAS: `truenas_admin@mallexxx.duckdns.org`, каталог `/mnt/RED_2TB/docker/lampac`, адрес приложения `http://192.168.2.197:9118`. +- Обычный Docker Compose, не TrueNAS Apps. Контейнер `lampac`: официальный образ `ghcr.io/lampac-nextgen/lampac:1.87.3`, amd64, лимиты 2 ГБ RAM / 1 CPU; кэш TorrServer — 2 ГиБ на диске, порт опубликован только на LAN-адресе. GPU-транскодирование не настроено. +- Kraken остаётся на официальном arm64-образе 1.87.3 и прежнем адресе `http://192.168.1.14:9118`. +- Mac: `/Users/admin/Developer/lampac-scripts`, ветка `main`, [Gitea](https://git.mallexxx.duckdns.org/git_admin/lampac-scripts). +- Генератор: `/Users/admin/Developer/watchlist-sync`, ветка `main`, [GitHub](https://github.com/mallexxx/watchlist-sync). +- Точные Compose, init.conf, шаблоны и инструкция восстановления — [deploy/README.md](https://git.mallexxx.duckdns.org/git_admin/lampac-scripts/src/branch/main/deploy/README.md). Приватные ключи и рабочие конфиги с секретами в git не добавляются. + +## Konka и встроенные постеры + +Konka Q85: `192.168.2.124`, webOS 7.5.6; приложение `com.lampac.tv` версии 1.0.0 установлено через существующий Homebrew Channel с проверкой SHA256 пакета. Его `lampac_url` в localStorage: `http://192.168.2.197:9118`. Настройку root/privacy не меняли; Developer Mode не устанавливали. + +Пустые постеры имели два отдельных условия: NAS не получал TMDB напрямую, а клиентская автоматика могла выключать прокси. На ТВ сохранены **`proxy_tmdb=true` и `proxy_tmdb_auto=false`**. Одной настройки серверного poster fetcher недостаточно: при выключенном клиентском флаге CUB обращается к `image.tmdb.org` напрямую. + +На NAS отдельный контейнер `lampac-egress` предоставляет SOCKS только внутри сети Lampac, без опубликованного порта. Он использует существующий авторизованный маршрут `kraken-user` через Caddy/VLESS и Kraken. Его приватный конфиг: `config-module/egress.json`, mode0640, group1000; шаблон без клиентского ID — в репозитории. + +Прокси `socks5://lampac-egress:1080` включён в: + +- `tmdb.proxyapi` и `tmdb.proxyimg` — встроенные TMDB/CUB; +- `posterApi` — постеры карточек Catalog; +- `serverproxy.image` — обработчик изображений. + +**Глобальный прокси для HTTP/видео не включён.** Проверенный тестовый видеопоток шёл напрямую с LAN-адреса NAS. Общий `vless-proxy` не меняли; при проверке его исходящие TLS-соединения обрывались. Это наблюдение на дату настройки, а не установленная причина отказа общей инфраструктуры. + +Внутренний вход Caddy — `192.168.2.197:8443`, TLS-имя `vpn.mallexxx.duckdns.org`. На `192.168.2.197:443` находится интерфейс TrueNAS: использовать его вместо 8443 нельзя, сертификат другой. + +## Единая история без синхронизации SQLite + +Рабочая TimeCode находится на TrueNAS в постоянном `database/TimeCode.sql`. Kraken-клиент обращается к тому же серверу через HTTPS API. **Двусторонней репликации SQLite, rsync БД или общего сетевого файла БД нет.** + +История из Kraken перенесена один раз штатными `/timecode/dump` → `/timecode/set`; сохранены все 9 записей и их прогресс. Оба сервера используют одинаковый серверный `instance.uid`; профили остаются отдельными областями. Старая БД Kraken оставлена для отката. + +В существующем HTTPS-сайте Caddy выделен префикс `https://git.mallexxx.duckdns.org/lampac-history` с точным списком TimeCode-маршрутов. Без правильного `history_key` — 404; прочие маршруты Lampac наружу через него не открыты. Gitea продолжает работать. Выполнен reload Caddy, не перезапуск общего сервиса. + +Ключ транспорта называется **`history_key`, не `token`**: `token` в Lampac меняет владельца данных. В сгенерированном клиенте и чтение, и запись должны независимо передавать `uid`; это проверяется исполнением реальных URL-методов в тесте. + +Kraken: приватный результат `scripts/build_timecode.py` установлен в `config-module/timecode-plugin.js`, примонтирован в `/lampac/module/Sync/TimeCode/plugin.js:ro`. NAS использует штатный локальный TimeCode-клиент. Межсайтовые WebSocket-уведомления не добавлялись; прогресс читается при обновлении карточки/таймлайна. + +## Каталог и отметки Obsidian + +Единственный генератор и писатель Obsidian остаётся на Kraken. Ночной `/home/kraken/scripts/watchlist-nightly.sh` после сборки каталога публикует готовые JSON на NAS через `publish_feed.py`. + +На NAS ключ приёма ограничен `restrict` и forced command `receive_feed.py --root /mnt/RED_2TB/docker/lampac`. Принимаются только файлы каталога; SQL, ссылки, пути вне разрешённого списка и код отклоняются. Файлы заменяются атомарно по одному, карточки раньше основного манифеста. Проверена публикация 544 файлов. + +`watchlist-sync` читает общий HTTPS dump через `LAMPAC_TIMECODE_DB` в приватном `.env`. Проверен dry-run: 9 записей, изменений списка не внесено. Второй генератор/писатель на NAS не запущен. Порог отметки фильма — 90%; существующее поведение сериалов этим изменением не расширялось. + +## Данные и бэкапы + +Постоянные `wwwroot` и `data` при первой установке нужно заполнить из образа до запуска. Пустые bind mounts скрывают и frontend, и резервные Cloudflare IP-файлы; при недоступной сети это приводило к падению старта. Конфиги должны читаться uid1000: режим0600 после SCP для `init.conf` не подходит. + +Контейнер пишет от uid1000/gid1000, администратор NAS — uid950 в группе1000. **`TimeCode.sql`, `-wal`, `-shm` должны иметь mode0664 и group1000.** Read-only SQLite-подключение тоже может создавать WAL/SHM. Первый бэкап от uid950 создал их с0644, после чего сервер не мог записывать прогресс. Владельцы/права исправлены, старые подключения закрыты перезапуском только Lampac. + +`scripts/backup_history.py` перед открытием проверяет group write, закрывает подключения, использует SQLite online backup и проверку целостности. NAS crontab: ежедневно04:27 по времени хоста, хранение30 дней в `backups/timecode`. Часовой пояс приложения — Asia/Novosibirsk; расписание cron не следует автоматически считать этим часовым поясом. Это локальный бэкап, не синхронизация БД. + +После исправления проверена API-запись **до и после повторного бэкапа**; все 9 записей и значения прогресса сохранены. Для отката сохранены прежние Compose/Caddy/настройки и снимки истории. Живую БД обычным копированием не подменять. + +## Что проверено + +- HTTP200 и окончание запуска `Configure complete`; одного Docker healthcheck недостаточно. +- Реальный поиск «дюна» на Konka через экранную клавиатуру: результаты получены, JavaScript-ошибок нет, вкладка watchlist одна. +- На видимом экране: 7 встроенных и28 каталожных постеров загружены, сломанных0; настройки прокси подтверждены после перезагрузки приложения. +- Плеер Lampac декодировал тестовый H.264-поток640×360 с NAS: время увеличивалось, `readyState=4`, ошибки отсутствовали. Непрерывный пяти-/десятиминутный тест, реальная онлайн-раздача и звук на слух отдельно не подтверждены. +- 10 автоматических тестов: 5 поиска, 3 развёртывания/истории/бэкапа, 2 чтения TimeCode API. +- Изменения запушены в обе репозитории; локальные копии в Developer обновлены. Временный установщик на роутере18085, ролик и диагностические SSH-перенаправления удалены. + +Связано: [[personal/tech/lampac-deployment]], [[personal/tech/konka-q85-novosibirsk]], [[family/how-to/truenas-infrastructure]], [[personal/plans/lampac-watched-to-watchlist]]. diff --git a/personal/tech/macos-voom-cleanup.md b/personal/tech/macos-voom-cleanup.md new file mode 100644 index 00000000..cc5adb6a --- /dev/null +++ b/personal/tech/macos-voom-cleanup.md @@ -0,0 +1,265 @@ +--- +title: 'macOS — Voom, Eagle и автоматическая очистка' +type: how-to +status: active +created: '2026-10-21' +updated: '2026-10-21' +tags: + - macos + - maintenance + - voom + - docker + - eagle-dashboard +aliases: + - Автоматическая очистка Mac + - Voom и Eagle +related: + - '[[personal/tech/docker-mac-disk-issues]]' + - '[[personal/tech/eagle-dashboard-launchd]]' + - '[[personal/projects/virfield]]' +--- +# macOS — Voom, Eagle и автоматическая очистка + +Состояние проверено **21 октября 2026** по установленным скриптам, конфигурациям и включённым заданиям Eagle. Это описание действующих правил; разовые ручные удаления могли быть шире автоматической политики. + +## Сетап и расписание + +Управление — [Eagle Dashboard → Crons](http://127.0.0.1:8880/?tab=crons). Eagle управляет пользовательскими LaunchAgent macOS; отдельных дублирующих расписаний в Codex нет. Все четыре задания включены. `RunAtLoad=false`: загрузка задания сама по себе не запускает очистку. + +| Задание в Eagle | Label | Расписание | +|---|---|---| +| Очистка артефактов и симуляторов — каждые 2 часа | `local.voom.cleanup` | Каждые 7200 секунд | +| Docker — кэш и старые образы | `com.voom.docker-policy` | Каждые 7200 секунд | +| Бэкапы Balda и Virfield — хранить сутки | `com.voom.backup-retention` | Каждые 7200 секунд | +| Штатная очистка кэшей — воскресенье, 12:00 | `local.voom.weekly-caches` | Воскресенье, 12:00 по локальному времени Mac | + +Период проверки и возраст удаления — разные параметры: например, бэкапы проверяются раз в два часа, а удаляются после суток. + +### Установленные файлы + +Базовый каталог: `/Users/admin/.local/share/voom-cleanup/` (`~/.local/share/voom-cleanup`). Python: `/opt/homebrew/bin/python3`. Voom: `~/.local/bin/voom`. + +| Файл | Назначение | +|---|---| +| `cleanup.py` | Основной проход: временные файлы, Xcode/SwiftPM, симуляторы, merged worktree | +| `tmp_cleanup.py` | Помощник для `/private/tmp`, вызывается основным проходом | +| `docker_cleanup.py`, `docker_images.py` | BuildKit, наблюдение за ссылками образов и их ограниченная очистка | +| `docker-policy.json` | Параметры Docker | +| `docker-image-observations.json` | История наблюдаемых ссылок контейнеров; нужна для отсчёта 30 дней | +| `backup_cleanup.py`, `backup-policy.json` | Суточное хранение явно перечисленных бэкапов | +| `weekly-caches.py` | Штатные команды uv, npm и Homebrew | + +Скрипты используют блокировки, чтобы повторный запуск того же задания не шёл параллельно предыдущему. + +### Что сейчас действительно включено + +На момент проверки существуют все три файла разрешения: + +- `enable-deletion` — основной проход с `--apply`. +- `enable-docker-deletion` — Docker с `--apply`. +- `enable-tmp-deletion` — отдельное разрешение удаления из `/private/tmp`. + +**Очистка `/private/tmp` включена.** Основной скрипт вызывает `run_cleanup(apply and not args.scan_only)`. Прежний вызов `run_cleanup(False)` заменён. Одного изменения описания Eagle для включения недостаточно — проверены и код, и файл разрешения. + +Бэкапы запускаются отдельным заданием с `--apply`, без дополнительного файла разрешения. Немедленный разовый запуск при установке этого задания не выполнялся; расписание включено. Успех конкретного прохода проверяется по отчёту, отдельно от факта включения задания. + +## Worktree и артефакты Xcode/SwiftPM + +### Worktree + +Репозиторий: `~/DuckDuckGo/apple-browsers.git`. + +- Voom удаляет вмерженные worktree без локальных изменений; merge через squash/rebase проверяется с GitHub. +- `main`, ветки Git, заблокированные worktree и worktree с защищаемыми локальными изменениями сохраняются. +- Используются `--remove-merged-worktrees --github-merged-worktrees` с явным исключением `main`. + +### Стандартный DerivedData + +Каталог: `~/Library/Developer/Xcode/DerivedData`. + +Проектная папка подходит для удаления, если: + +1. Её `WorkspacePath` указывает на уже отсутствующий проект в домашнем каталоге; либо +2. Во всём содержимом нет изменений **48 часов**, и `LastAccessedDate` не указывает на недавнее обращение. + +Требуются метаданные проекта или узнаваемые признаки Xcode output. Общие кэши без таких признаков сохраняются. Для возрастного правила учитывается максимум `mtime` и `ctime` всех элементов, включая каталоги; перед удалением условие перепроверяется. + +### Внутрипроектные папки + +Поиск **рекурсивный** внутри: + +- `~/Developer` +- `~/DuckDuckGo` +- `~/.codex/worktrees` +- `~/Documents/Codex` +- `~/Documents/code` + +| Имена каталогов | Условие | +|---|---| +| `.build` | 48 часов без изменений во всём дереве и признаки SwiftPM: `workspace-state.json`, `build.db`, `debug.yaml` или `release.yaml` | +| `dd`, `deriveddata`, `DerivedData`, `.build-dd` | 48 часов без изменений во всём дереве и признаки Xcode output | + +Имена Xcode-папок сравниваются без учёта регистра. Признаки Xcode: например, `Build/Intermediates.noindex/XCBuildData/build.db`, либо `info.plist` с `WorkspacePath` вместе со стандартными каталогами результатов сборки. Одного имени папки недостаточно. + +При обходе пропускаются ссылки, `.git`, `node_modules`, `.venv`, `venv`, `target`, `.Trash`, `SourcePackages` и сами корни артефактов. Внутри найденного корня возраст проверяется по всему содержимому. + +Основной проход откладывает удаление при обнаружении процессов сборки `xcodebuild`, `swift-build`, `swiftc`, `swift-frontend`, `clang`, `ld` или при невозможности проверить процессы. + +### Исключения VS Code/DDG — 2026-10-23 + +Подкаталоги `.build/xcode-editor/` и `.build/vscode-local/` сохраняются целиком, включая профиль `config.json` и manifest интеграции. Если `.build` содержит эти подкаталоги и признаки SwiftPM, очистка выбирает только остальные дочерние элементы старше 48 часов; свежесть защищённых настроек не удерживает старые соседние артефакты. Перед удалением повторно проверяется, что защищённые подкаталоги не появились в выбранной целиком `.build`. + +То же исключение применяется в `/private/tmp`: эти два подкаталога не обходятся и защищены повторной проверкой перед unlink. Корневой `buildServer.json` вне `.build` проектное правило не затрагивает. Правила DerivedData и удаления worktree этим изменением не менялись. + +## Симуляторы Xcode + +- Используется **непустой `lastUsedAt`** из `device.plist`. +- Давность последнего использования — **от 7 дней**. +- Последнее использованное устройство сохраняется; при одинаковой максимальной дате сохраняются все совпадения. +- Запущенные устройства сохраняются. Непосредственно перед удалением повторно проверяются даты и состояние через `simctl`; удаляется только `Shutdown`. +- Устройства без даты использования не удаляются. Возраст создания и `lastBootedAt` не подставляются вместо `lastUsedAt`. +- Принудительной остановки и `simctl delete all` нет. + +## `/private/tmp`: 4 часа + +Удаление включено в основном проходе раз в два часа. + +- Рассматриваются обычные файлы любых расширений, включая исходники, патчи, логи и результаты сборок. +- Условие: **не менее 4 часов с `mtime` каждого файла**. `ctime` и возраст родительской папки не используются. +- Старый файл может быть удалён рядом со свежим; свежий файл внутри старой папки сохраняется. +- Открытые файлы сохраняются. Открытый каталог или рабочий каталог процесса защищает всё своё поддерево. Проверка — через `lsof`. +- Ссылки, сокеты, специальные файлы, чужие владельцы и другие файловые системы пропускаются. Непустые каталоги целиком не удаляются; пустые каталоги скрипт также отдельно не убирает. +- Перед удалением перепроверяются inode, размер, `mtime`, принадлежность, открытые пути; проход по каталогам выполняется без следования ссылкам. +- При недоступности `lsof` удаление запрещено. При активной сборке удаление откладывается. + +## Бэкапы Balda и Virfield: сутки + +Порог — **24 часа**. Для снимка берётся самая новая дата создания (`birthtime`) или `mtime` во всём дереве. Это защищает свежие APFS-клоны, у которых сохранились старые даты содержимого. Недавнее изменение любого элемента откладывает удаление всего снимка. + +### Balda + +Полностью проверяются отдельные снимки внутри: + +- `~/.local/share/balda-workspace/backups/` +- `~/Docker/balda-mm-mac/state/setup-backups/` +- `~/Docker/balda-mm-eagle/state/setup-backups/` + +Только в корнях `~/Docker/balda-mm-mac/` и `~/Docker/balda-mm-eagle/` дополнительно выбираются: + +- Каталоги/файлы `backup-*`, `state-backup-*`, `state-failed-*`. +- Файлы с шаблонами `*.before-*`, `*.backup`, `*.backup-*`, `*.bak`, `*.bak-*`. + +Рабочие `state`, текущие Compose/config-файлы и рабочая БД под эти шаблоны не попадают. + +### Virfield + +- `backups/` и `migration-backups/` внутри `~/.virfield-tart/` и `~/.virfield-v2/`. +- `~/.virfield-v2/upgrades/`. +- `~/.virfield-v2/retained-maintenance/`. +- В корнях `~/.virfield-tart/` и `~/.virfield-v2/` — только резервные файлы `*.before-*`, `*.backup`, `*.backup-*`, `*.bak`, `*.bak-*`. + +Рабочие `state.db`, `images`, `leases`, `nodes`, текущие конфиги и обычные диски VM целями не являются. + +### Защита бэкапов + +- Используемый снимок или его содержимое сохраняются по проверке `lsof`; использование выделенного каталога бэкапов также защищает его содержимое. +- Возраст, inode и открытые пути проверяются повторно перед удалением. +- Корни со ссылками в пути, чужие владельцы, другие файловые системы, сокеты и специальные файлы пропускаются с записью в отчёт. +- Ссылки внутри снимка не обходятся. Для удаления read-only каталогов снимка добавляются права только владельцу каталогов; файлы и внешние цели ссылок не изменяются. +- Последний бэкап не закреплён навсегда: по этой политике все подходящие снимки старше суток могут быть удалены. + +## Docker + +Цель жёстко закреплена: context **`colima`**, builder **`colima`**, socket `/Users/admin/.colima/default/docker.sock`. Перед работой проверяется соответствие контекста и работающего builder. Другие и неактивные builders не запускаются. + +### BuildKit cache + +| Тип | Минимальное неиспользование | +|---|---:| +| Обычный поддерживаемый build cache | 14 дней от `LastUsedAt` | +| Cache mounts и распознанные слои установки зависимостей | 30 дней от `LastUsedAt` | + +Зависимости распознаются по типу `exec.cachemount` и описаниям с apt/apk/dnf/yum, pip/uv, npm/pnpm/yarn, cargo, gem/bundler/composer, `go mod`. + +Сохраняются `InUse`, `Shared`, internal/frontend, неизвестные типы и записи без корректной даты. Удаление ограничено точным списком cache-ID и фильтром возраста; BuildKit повторно проверяет использование. Broad `--all` не используется. + +### Образы: 30 дней без наблюдаемых ссылок + +Область автоматической очистки: + +- `execution-broker` и `execution-broker-*`. +- `execution-prism-adapter`. +- `balda` и `balda-*`, включая старые обычные версии. +- Образы без тегов и без digest-reference. + +Условия и исключения: + +- Нет ссылок **любых контейнеров**, включая остановленные, не менее 30 дней по журналу наблюдений. +- При появлении ссылки таймер сбрасывается; после исчезновения начинается новый отсчёт. +- Сохраняются образы, найденные в доступных файлах конфигураций и переменных окружения работающих контейнеров. +- Для обычных версий сохраняется одна самая новая сборка **для каждого repository** по Docker `Created`; одинаковые максимальные даты сохраняются все. Это дата сборки, а не сравнение номеров версий. +- Явно тестовые/временные/резервные Balda-теги (`test`, `temp`, `tmp`, `scratch`, `fixture`, `acceptance`, `prNN`, `backup`, `bak`, `rollback`) могут истечь и без сохранения «последнего тестового» образа. +- Для Broker дополнительно защищены теги/имена с `rollback`, `base`, `accepted`, `approved`, `stable`, `latest`, `current`, `production`, `prod`. Эти закрепления могут сохранять больше одной версии. +- Для Balda постоянная защита всех исторических версий отключена; действуют ссылки, конфигурации, последняя обычная версия и срок 30 дней. +- Перед каждым удалением повторно проверяются Docker inventory, конфигурации, ссылки, возраст наблюдений и теги. Удаление — по точному ID без `--force`, с `--no-prune`. + +`docker-image-observations.json` отражает периодические наблюдения за ссылками контейнеров. Он **не даёт полного last used**: использование в `FROM` и короткоживущие контейнеры между проверками могут не попасть в историю. Нельзя подменять эти 30 дней возрастом создания образа. История начата 21 октября 2026; потеря журнала запускает отсчёт заново. + +### Известные ограничения Docker + +- Именованные образы вне списка выше сохраняются независимо от отсутствия контейнеров. Например: **Mattermost**, `budget-*`, `workspace-smb`, `workspace-nfs-client`, `ssh-manager-isolation-fixture`, `go-cli*`, базовые Go/Node/Python и прочие внешние образы. +- Для Mattermost отсутствие ссылок контейнеров/конфигураций проверено; причина сохранения — ограничение списка разрешённых имён. +- При нескольких тегах удаление по ID может закончиться конфликтом; скрипт сохраняет образ и пишет `kept_conflict_or_error`. +- Полной очистки графа скрытых промежуточных образов нет: используется `/system/df` и `--no-prune`. Проявившийся безымянный родитель должен отдельно попасть под возрастное правило; ручная дочистка всей цепочки была шире автоматической политики. +- **Контейнеры, volumes и networks автоматически не удаляются.** Удаление Broker/restore-контейнеров 21 октября было отдельным ручным действием. + +## Еженедельное обслуживание кэшей + +По воскресеньям в 12:00 выполняются штатные команды: + +```sh +/opt/homebrew/bin/uv cache prune +/opt/homebrew/bin/npm cache verify +/opt/homebrew/bin/brew cleanup +``` + +Возраст и выбор данных определяют сами инструменты. `npm cache verify` проверяет/обслуживает кэш; полного удаления всего npm-кэша правило не обещает. Автообновление Homebrew на время этого задания отключено. + +## Что ещё не входит в автоматические правила + +- `WebKitBuild` из Downloads и архив прошивки iPhone `.ipsw`: 21 октября удалены вручную, общих правил для них нет. +- Android SDK исключён из согласованной очистки; отдельно его не трогать. +- Claude VM bundles/Cowork VM не очищаются этим сетапом. +- Все произвольные кэши приложений, все Docker images или все файлы Downloads не охватываются широким правилом. + +## Проверка, отчёты и управление + +JSON-отчёты в `~/.local/share/voom-cleanup/`: + +| Отчёт | Что смотреть | +|---|---| +| `last-report.json` | Xcode/SPM, симуляторы, worktree, сводка tmp | +| `tmp-report.json` | Кандидаты tmp, `deleted_files`, `deletion_deferred`, ошибки | +| `docker-report.json` | BuildKit, образы, причины сохранения, результаты удаления | +| `backup-report.json` | Бэкапы, возраст, занятые пути, результаты и ошибки | +| `weekly-caches-report.json` | Exit code и вывод каждой штатной команды | + +Основные логи: `launchd.log`, `launchd.stderr.log`; для еженедельного задания — `weekly-caches.log`, `weekly-caches.stderr.log`. + +Включённое задание и поле `apply` сами по себе не подтверждают освобождение места: проверять время отчёта, `results`, ошибки и причины отложенного удаления. Основной скрипт при активной сборке может завершиться до обновления `last-report.json`; смотреть также лог и отдельный `tmp-report.json`. + +Предпросмотр без удаления: + +```sh +/opt/homebrew/bin/python3 ~/.local/share/voom-cleanup/cleanup.py --scan-only +/opt/homebrew/bin/python3 ~/.local/share/voom-cleanup/tmp_cleanup.py +/opt/homebrew/bin/python3 ~/.local/share/voom-cleanup/docker_cleanup.py +/opt/homebrew/bin/python3 ~/.local/share/voom-cleanup/backup_cleanup.py +/opt/homebrew/bin/python3 ~/.local/share/voom-cleanup/weekly-caches.py --check +``` + +`--scan-only` у основного скрипта показывает кандидатов артефактов/симуляторов/tmp, но пропускает Voom-проход worktree. Эти проверки могут обновлять отчёты; Docker также обновляет журнал наблюдений. + +Для паузы задания использовать Eagle → Crons. Чтобы отключить только tmp, можно переименовать `enable-tmp-deletion`; остальные задачи основного прохода сохранят свои настройки. Для бэкапов отдельного файла разрешения нет — управлять их заданием в Eagle. Не создавать второе расписание поверх существующих labels. + +Связанные заметки: [[personal/tech/eagle-dashboard-launchd|Eagle и launchd]], [[personal/tech/docker-mac-disk-issues|Docker на Mac: диск и история очисток]], [[personal/tech/docker_colima_setup|Colima]], [[personal/projects/virfield|Virfield]]. diff --git a/personal/tech/xray-reverse-tunnel-kraken-truenas.md b/personal/tech/xray-reverse-tunnel-kraken-truenas.md index db9a811b..bc4b0e8a 100755 --- a/personal/tech/xray-reverse-tunnel-kraken-truenas.md +++ b/personal/tech/xray-reverse-tunnel-kraken-truenas.md @@ -1,7 +1,7 @@ --- title: Xray Reverse Tunnel — Kraken ↔ TrueNAS created: '2026-09-01' -updated: '2026-09-23' +updated: '2026-10-30' type: tech namespace: personal tags: @@ -18,8 +18,7 @@ related: - '[[family/how-to/kraken-access]]' - '[[family/how-to/rasputin-router]]' - '[[personal/tech/xray-outbound-subscription-3xui]]' -downgrade_to_26.4.25: >- - portal+bridge BOTH pinned to 26.4.25; required but insufficient by itself. +downgrade_to_26.4.25: portal+bridge BOTH pinned to 26.4.25; required but insufficient by itself. verified_fix_2026_09_02: >- Kraken bridge must use Docker network_mode: host; Docker bridge/NAT reset the VLESS reverse mux. REALITY+Vision verified end-to-end with Kraken egress. @@ -27,58 +26,33 @@ verified_fix_2026_09_02: >- # Xray Reverse Tunnel — Kraken ↔ TrueNAS -> ## ✅ 2026-09-30 — ПРИЧИНА НАЙДЕНА И УСТРАНЕНА: `dockerd` УБИТ SIGKILL MID-RESTORE → КОНТЕЙНЕРЫ ЗАПИСАНЫ КАК «ОСТАНОВЛЕННЫЕ ВРУЧНУЮ» -> -> **Запрос Alex:** почему не работают профили 3x-ui (`reality primary`/`kraken-user`, `cf fallback`/`vless-space`) и обратный прокси через Кра́кен. -> -> **Итог: `kraken-user` МЁРТВ, `user1` и `vless-space` ЖИВЫ.** Проверено локальными docker-клиентами на Mac: -> -> | Профиль | Ожидаемый egress | Фактический egress | Статус | -> |---|---|---|---| -> | `user1` (direct) | TrueNAS `90.189.160.148` | `90.189.160.148` | ✅ жив | -> | `kraken-user` (via reverse) | Кра́кен `92.62.77.233` | `92.62.77.233` **после фикса** | ⚠️ **был мёртв 3 дня** | -> | `vless-space` (подписка) | Стокгольм `195.72.61.192` | `195.72.61.192` (geo: Sweden/Stockholm) | ✅ жив | -> -> ⚠️ **Питфолл замера:** `api.ipify.org` отдаёт разные IP в зависимости от edge Cloudflare — при первом тесте `vless-space` показал `104.28.225.223` (это Cloudflare, НЕ egress). **Проверять egress через `ip-api.com` / смотреть geo**, а не только `api.ipify.org`. -> -> ### 🔴 ИСТИННАЯ ПРИЧИНА (не конфиг, не версия, не REALITY) -> -> **`xray-reverse-bridge` на Кра́кене был `Exited (2)`, up-time НОЛЬ — 3 дня.** Кра́кен жив (uptime 2 дня 21 ч, LAN `192.168.1.14`), диск `sda1` примонтирован, docker `active`. Мёртв был **только контейнер**. -> -> **Полная цепочка (доказано journalctl):** -> -> | Время (локальное +06:00, boot 2026-09-27 16:23) | Событие | -> |---|---| -> | 16:24:05 | `dockerd` PID **1385** стартует, начинает `Restoring containers` | -> | 16:24:08 | `failed to register container name ... "name is reserved"` для sonarr/radarr/jellyfin/prowlarr/rclone/transmission/hermes-kraken/portainer/cloudflared | -> | 16:24:40 | восстанавливает контейнер `8a0246d2...` (создаёт scope) | -> | **16:25:35** | **`docker.service: State 'stop-sigterm' timed out. Killing.` → `Killing process 1385 (dockerd) with SIGKILL` → `Main process exited, code=killed, status=9/KILL` → `Failed with result 'timeout'`** | -> | 16:25:35 | systemd стартует docker заново → `dockerd` PID **3750** | -> | 16:26:28 | `Daemon has completed initialization` | -> -> **Механизм отказа:** systemd убил `dockerd` **посреди восстановления контейнеров**. Контейнеры, не успевшие восстановиться, были записаны в состоянии `exited` с **`Restarts=0`** — а поскольку daemon был убит, а не остановлен gracefully, политика `unless-stopped` сохранилась как «остановлен вручную». Новый daemon (PID 3750) их **не поднял** — для него это были намеренно остановленные контейнеры. -> -> 🔴 **Это НЕ bridge-специфичный баг.** Тем же сбоем одновременно легли три независимых контейнера: -> `radarr` Exit=137 (SIGKILL), `sonarr` Exit=0, `prowlarr` Exit=137 — у всех `Restarts=0`, все с политикой `unless-stopped`, все до сих пор `exited`. -> -> **Ключевой диагностический признак:** *«контейнер с `unless-stopped` лежит в `Exited`, `RestartCount=0`, а хост живёт неделями»* → смотреть `journalctl -u docker` на `stop-sigterm timed out` / `SIGKILL` вокруг времени последнего boot. Это не конфиг контейнера. -> -> ### ✅ ФИКС (применён 2026-09-30) -> ```bash -> ssh kraken@192.168.1.14 'cd /home/kraken/xray-reverse && sudo -n docker compose up -d' -> ``` -> Через ~2 с bridge установил канал: `dialing TCP to mallexxx.duckdns.org:12346` → `received request for udp:reverse:0`. -> **Проверка end-to-end:** SOCKS `:12345` → `92.62.77.233` (Кра́кен), портал `ESTABLISHED ::ffff:172.16.1.7:12346 ← ::ffff:92.62.77.233:1963`. -> -> ### ⚠️ ОСТАЁТСЯ ПРОБЛЕМОЙ (не устранено) -> 1. **Причина остановки docker при boot не найдена.** `TimeoutStartUSec=infinity`, но убил **stop**-путь (`TimeoutStopUSec=1min 30s`). Вероятно, кто-то/что-то инициировало `systemctl stop docker`/`restart` в 16:24-16:25 (watchtower? OMV?). **Требует отдельного разбора** — иначе повторится. -> 2. **`radarr`, `sonarr`, `prowlarr` до сих пор `exited`** — не подняты (вне задачи, но факт). -> 3. **Самолечения нет.** `unless-stopped` при таком сбое не помогает. Кандидат: systemd-timer или healthcheck, поднимающий bridge, если он down. -> -> ### Обратный прокси (Кра́кен → LAN TrueNAS) — НЕ работает, и это ОЖИДАЕМО -> Проверено живьём: портал в **исходном** состоянии (`routing: local → reverse-out` только), бридж — `inbounds: null`. Правило `192.168.2.0/24 → direct` отсутствует. То есть эксперимент 2026-09-23 **откачен корректно**, конфиги не тронуты. Направление «в чужую приватную сеть» reverse-каналом **не реализуется по дизайну Xray** — см. раздел «🚫 АРХИТЕКТУРНЫЙ ПРЕДЕЛ» ниже. Обратный прокси в смысле *egress локальных клиентов через Кра́кен* (`kraken-user`) — **работает** после фикса. -> -> **Локальные клиенты для проверки (на Mac):** `/Users/admin/xray-test/clients/{user1,kraken-user,vless-space}.json`, порты `10801/10802/10803`. Запуск: `docker run -d --name t- -v /Users/admin/xray-test/clients/.json:/etc/xray/config.json:ro -p : teddysun/xray:26.4.25`. В образе **нет `curl`** — тестировать отдельным контейнером `--network host curlimages/curl`. +## Эксплуатация и автозапуск + +Актуально на 2026-10-30. Туннель работает: HTTPS-запрос с TrueNAS через SOCKS `127.0.0.1:12345` выходит с адреса Kraken. При проверке Kraken: `217.29.24.185`, TrueNAS напрямую: `90.189.160.148`. WAN-IP может меняться; сравнивать с живым запросом на Kraken. + +**Причина остановки bridge:** Monit перезапускал containerd и Docker во время загрузки хоста. Docker не завершал остановку за 90 секунд и получал SIGKILL; bridge сохранялся с `HasBeenManuallyStopped=true`, поэтому `unless-stopped` не поднимал его. + +**Действующая настройка Kraken:** +- `/etc/monit/conf.d/docker` — только уведомления при отсутствии процесса и ошибке Docker socket; команд запуска, остановки и перезапуска нет. Docker управляется systemd. +- Зависимость Docker от монтирования диска в `docker.service.d/mounts.conf` сохранена. +- `/etc/systemd/system/xray-reverse-bridge.service` — включённый oneshot-юнит после `docker.service`, подключён к `multi-user.target` и `docker.service`. Выполняет из `/home/kraken/xray-reverse`: + `docker compose up -d --no-build --pull never xray-reverse-bridge`. +- Юнит обеспечивает запуск при загрузке хоста и запуске Docker; это не периодическая проверка здоровья туннеля. `ExecStop` отсутствует. +- Bridge: `teddysun/xray:26.4.25`, `network_mode: host`, `restart: unless-stopped`. + +**Проверка:** + +```bash +# Kraken: SSH, служба и контейнер +ssh kraken@ssh-kraken.qentra.top 'systemctl is-active xray-reverse-bridge; docker inspect xray-reverse-bridge --format "{{.State.Status}}"; curl -4 -fsS --max-time 15 https://api.ipify.org' + +# TrueNAS: реальный запрос через туннель +ssh truenas_admin@mallexxx.duckdns.org 'curl -4 -fsS --max-time 20 --socks5-hostname 127.0.0.1:12345 https://api.ipify.org' +``` + +Оба запроса должны завершиться успешно и показать один адрес. Сквозной запрос и новое SSH-подключение проверены; Docker и Cloudflare не перезапускались. Автозапуск настроен и включён, но перезагрузкой хоста пока не проверен. + +Для повторного запуска bridge без перезапуска Docker: `sudo systemctl restart xray-reverse-bridge.service`. > ## ✅ 2026-09-23 — КАНАЛ ЖИВ, ПРОВЕРЕНО END-TO-END ПОСЛЕ ПОЧИНКИ 20.09. IP КРА́КЕНА ИЗМЕНИЛСЯ. > diff --git a/work/_index.md b/work/_index.md index f1150f9d..3485cd00 100755 --- a/work/_index.md +++ b/work/_index.md @@ -1,3 +1,5 @@ # Work [[projects/ios-duck-ai-toggle]] | [[projects/ios-demo-tracker]] | [[projects/vscode-migration/vscode-migration]] + +[[macos-ui-tests-pr7562-pr7592-results-2026-10-23|macOS UI tests — результаты PR #7562 и #7592, работы остановлены]] diff --git a/work/macos-ui-tests-pr7562-pr7592-results-2026-10-23.md b/work/macos-ui-tests-pr7562-pr7592-results-2026-10-23.md new file mode 100644 index 00000000..5fd1825d --- /dev/null +++ b/work/macos-ui-tests-pr7562-pr7592-results-2026-10-23.md @@ -0,0 +1,136 @@ +--- +date: '2026-10-23' +status: in-progress +tags: + - work + - macos + - testing + - ci +prs: + - 7562 + - 7592 +--- +# Актуализация: минимальное разделение PR, 23 октября 2026 + +Пользователь поручил переделать стек с минимальным первым PR. Предыдущие разделы ниже сохранены как история; их heads и результаты не являются проверками новой реализации. + +- PR #7562: `f85df79269ea67f5f13d776f1cc7d78bf2bc1069`, GitHub подтвердил **3 изменённых файла**: только producer/consumer/top-level UI workflows. Сборка выполняется один раз на существующую комбинацию OS/Xcode/distribution, а не на каждый класс и user mode. Матрица OS/Xcode сохранена; единого бинарного бандла для разных Xcode этот минимальный diff не вводит. Схемы, test plans, fixtures, Swift, Sync/Performance, удалённая инфраструктура и архивирование приложения восстановлены к исходной базе. +- PR #7592: `4283b2cd33ea34f4370799d5b0b94d0b0c502248`, база — PR #7562, GitHub подтвердил **8 изменённых файлов**. Только отдельный macOS 12 consumer, Swift symbol bridge/discovery, его документация и 6 проверок, macOS 12 guards в двух fixtures и Intel-срез notarized приложения. Native macOS 14+ адаптер не вызывает. +- История сохранена обычными коммитами и merge; force-push/rebase не выполнялись. Оба PR опубликованы, описания обновлены под текущий diff. +- Локальные `build-for-testing` первого и второго PR завершились успешно (сборка без выполнения UI). Проверки адаптера **6/6 passed**, strict SwiftLint изменённых Swift-файлов — 0 нарушений, actionlint и `git diff --check` прошли. +- Локальный диагностический `-enumerate-tests` неожиданно поднял UI runner и завершился ошибкой Automation timeout; это **не pass**. Пользователь запретил локальные UI-прогоны. Пробник остановлен, оставшихся локальных процессов этого пробника не обнаружено; UI дальше только CI/VM. +- [CI 39949300020](https://github.com/duckduckgo/apple-browsers/actions/runs/39949300020), exact parent head `f85df79…`: DMG, HTTPSUpgradeUITests, обе user modes, существующие OS 14/15/27. На момент записи ещё выполняется notarized app producer; конечного результата переноса и запуска бандла пока нет. +- Полная runtime-квалификация новой версии адаптера на macOS 12 **не выполнена**. Старые loader/runtime результаты нельзя засчитывать. Сохранённый SSH config прежней VM отсутствует; новые TCC grants не выдавались. +- Автоматизация `ci-pr-7562` остаётся PAUSED; активная работа по переделке возобновлена прямым поручением пользователя. + +Логи и сохранённые исходные версии: `/private/tmp/ddg-pr-scope-20261023/`. Новые runtime failures/результаты следует записывать с точным head. + +--- + +# macOS UI tests: результаты PR #7562 и #7592 + +Статус: **работы остановлены пользователем 23 октября 2026 года**. Задача не завершена. Автоматизация `ci-pr-7562` переведена в `PAUSED`; активных локальных контроллеров квалификации не обнаружено. Новые сборки, тесты и изменения не запускаются. Все три незавершённых GitHub Actions run отменены; терминальные статусы GitHub подтверждены. Завершённый Performance run сохранён. + +## Стек и сохранённый код + +- [PR #7562](https://github.com/duckduckgo/apple-browsers/pull/7562): `main → codex/prebuilt-macos-ui-tests`, head `a5db06b1ad14f59ed41821b3ec9561670231486c`. +- [PR #7592](https://github.com/duckduckgo/apple-browsers/pull/7592): `codex/prebuilt-macos-ui-tests → codex/macos12-test-support`, head `12a187ff86d6599293f3061e125eed69e5355bf7`. +- Оба PR — draft; изменения закоммичены и опубликованы. Оба рабочих дерева при остановке чистые. +- PR1 содержит общую схему артефактов и штатный native XCTest для macOS 14+. PR2 добавляет только поддержку macOS 12; дочерний PR можно исключить без включения адаптера в native-путь. +- Рабочие деревья: `/Users/admin/DuckDuckGo/apple-browsers.git/prebuilt-macos-ui-tests` и `/Users/admin/DuckDuckGo/apple-browsers.git/macos12-test-support`. + +## Что исправлено + +1. Упаковщик больше не ожидает фиксированное имя приложения: находит единственный экспортированный `DuckDuckGo*.app` и сохраняет его версионированное имя. Коммит PR1 `c9fb27e22f60187c78295d5b432f0ac2c5476bec`. +2. Native-потребители UI, Sync и Performance устанавливают парное приложение через `--app`. Коммит `e49d10a40c50176d657af92189f3424d1e05f948`. +3. Исправлена конфигурация целевого приложения: после сборки с `TEST_TARGET_NAME=` явно сбрасывается `UseUITargetAppProvidedByTests=false`. Итог native-тестов берётся из конечного отчёта `xcresulttool`, включая фактическое число тестов, skips и failures. Коммит `4a81f3027b817d506bd90fb874ab0ec4fa3213d7`. +4. Для четырёх тестовых UI runner задан явный `CODE_SIGN_ENTITLEMENTS` с App Sandbox=false: class fixtures должны запускать reset helper. Один `ENABLE_APP_SANDBOX=NO` не отключал автоматически добавляемую Xcode песочницу; это воспроизведено отдельной native-сборкой Xcode 27.1, явный файл entitlements проверен через codesign. Подпись и песочница браузера не изменялись. Коммит `a5db06b1ad14f59ed41821b3ec9561670231486c`. +5. В macOS 12 адаптер сохраняет точные entitlements исходного native runner при переподписи копии Xcode 13; они входят в ключ compatibility cache. Коммит PR2 `40093aac23be577964e91e93b5527e87d2ced6af`. +6. Для Xcode<27 `build.sh` временно сохраняет project/local `Package.resolved`, позволяет этому компилятору разрешить граф по подходящим manifests и восстанавливает исходные файлы при успехе и ошибке. Проверена сохранность незакоммиченного lockfile после сбоя. Это исправляет прежний crash Xcode при surplus pins (`IDESwiftPackageCore / NSMutableArray insertObjects`, counts 42/41 и 41/40). + +## Подтверждённые проверки + +- Скриптовые проверки PR1: **29/29 passed**; PR2: **34/34 passed**. Это проверки скриптов, не полный runtime UI/Sync на всех OS. +- Проверки plist/project прошли. Для ранее изменённых Swift-файлов проходил strict SwiftLint; последние исправления затрагивали Python, конфигурацию и entitlements. +- [Performance run 39909721173](https://github.com/duckduckgo/apple-browsers/actions/runs/39909721173), точный head PR1 `a5db06b…`: **success**, фактически выполнено **5 тестов, 0 skipped, 0 failed**. Подтверждено конечной JSON summary и журналом job `120273644724`. Это результат конкретного Performance workflow, не всех suites/OS. +- Выборочно подтверждено реальное выполнение текущего native UI: [job 120275173593](https://github.com/duckduckgo/apple-browsers/actions/runs/39909724376/job/120275173593), HTTPSUpgradeUITests, macOS 14/internal: executed=4, skipped=1, failed=0, passed=true. Полный UI workflow не был завершён к моменту остановки. + +## Текущие CI runs + +- PR1 [UI 39909724376](https://github.com/duckduckgo/apple-browsers/actions/runs/39909724376): head `a5db06b…`, остановлен по поручению пользователя. +- PR1 [Sync 39909727852](https://github.com/duckduckgo/apple-browsers/actions/runs/39909727852): тот же head, остановлен по поручению пользователя. +- PR1 [Performance 39909721173](https://github.com/duckduckgo/apple-browsers/actions/runs/39909721173): success, сохранён завершённый результат. +- PR2 [UI 39909798220](https://github.com/duckduckgo/apple-browsers/actions/runs/39909798220): head `12a187f…`, остановлен по поручению пользователя. + +Перед запросами отмены эти незавершённые runs не имели подтверждённых job failures в проверенных снимках. Частично зелёные jobs не означают полный pass. Отмену по поручению пользователя нельзя считать падением кода. + +Конечные состояния подтверждены 23 октября 2026, **12:46:31 Asia/Bishkek** (06:46:31 UTC): UI PR1 39909724376 — completed/cancelled; Sync PR1 39909727852 — completed/cancelled; UI PR2 39909798220 — completed/cancelled; Performance PR1 39909721173 — completed/success. Снимок сохранён в `stopped-by-user.json`. + +## macOS 12: что действительно проверено + +Временная VM: `lease-376c79688b0798560d528772ecf89dd6`, ARM, macOS 12.7.6 / 21H1320, Xcode 13.4.1 / 13F100. Lease expiry: **24 октября 2026, 10:58:20 Asia/Bishkek** (04:58:20 UTC). + +- Пользователь разрешил unattended UI Automation; режим был настроен. +- Native readiness probe: **1/1 passed** — реальный запуск приложения, клик, чтение Accessibility. Это только проверка среды, не runtime PR. +- На текущем head PR2 `12a187f…` прошли **5/5 loader-only** проверок: DMG UI/Performance/Sync; App Store UI/Sync App Store. Приложение при них не запускалось. +- **Ни один runtime-этап текущего head на macOS 12 не начат.** +- Контроллер `head-12a187/qualify.py` завершён. Его первая попытка ошибочно запросила `appstore/performance`, которого нет в схеме и опубликованном manifest; получен `StopIteration`. Это ошибка локального сценария квалификации, а не missing-symbol/runtime-дефект PR. Список исправлен; недостающий App Store Sync loader отдельно успешно проверен. Исходный неуспешный журнал сохранён. +- Состояние перед остановкой: `awaiting_downloads_access`, `runtime_started=false`, `controller_terminal=true`; подтверждающий readiness marker не создавался. + +## Почему подготовка среды осталась незавершённой + +В предыдущем runtime-прогоне старого head `695ef15b09c8dd9df0fa1293a709e2208023b694` браузер создавал PDF, но XCTest runner зависал при чтении атрибутов файла в Downloads. TCC-журнал указал `kTCCServiceSystemPolicyDownloadsFolder` для `com.duckduckgo.UI-Tests.xctrunner`; screenshot xcresult показал модальный запрос **Legacy-Runner → Downloads**. + +У App Store браузера проверено наличие `com.apple.security.files.downloads.read-write=true`. Запрос относился к отдельному процессу runner. Этот же диалог перекрывал print panel, вызывая вторичные ошибки меню Save as PDF и AX timeout. Не следует менять selectors или пропускать PrintingTests, не устранив setup. + +Существующий `prepare-host.py` проверяет запуск/клик/AX, но не доступ реального XCTest runner к тестовым файлам. Автоматический воспроизводимый setup **не реализован до конца**; ручное разрешение в одной VM не решило бы эту недоработку. + +Автоматическая проверка разрешений отклонила попытку ограниченного нажатия OK для Downloads, потребовав явного согласия на persistent TCC-доступ. Изменения не выполнены; TCC.db не редактировалась, доступ не выдавался, Screen Sharing не использовался. + +Проверено `profiles status -type enrollment`: DEP No, MDM No. Штатная автоматическая доставка такого разрешения через PPPC требует user-approved MDM; сама эта VM к MDM не подключена. Источники: [Apple PPPC](https://support.apple.com/en-au/guide/deployment/dep38df53c2a/web), [официальная schema Apple](https://github.com/apple/device-management/blob/release/mdm/profiles/com.apple.TCC.configuration-profile-policy.yaml). + +Незавершённым остаётся Settings → NSPathCell → FileProvider coverage, полный UI, sandbox leg и Sync/Performance runtime текущего PR на macOS 12. Заявлений «production-ready» или «all mac12 suites pass» нет. + +## Какие старые результаты нельзя засчитывать + +На старых heads `e49… / 695…` UI/Performance jobs могли быть зелёными при class setup exception **No target application path specified via test configuration**. xcodebuild возвращал 0, observer показывал запланированное число тестов, хотя настоящий XCTest не выполнял методы. Эти зелёные статусы — ложные; новый parser и target configuration исправлены отдельно. + +Старый macOS 12 этап `critical-production` на `695…`: requested=34, executed=32, failed_methods=4, passed=false. Прошли 26 search-тестов и часть других методов, но это **не проверка текущего head**. Результаты экспортированы, восстановление keychain подтверждено. Последний SSH exit=255 не является первопричиной четырёх test failures; удалённый supervisor завершился ошибкой. + +Диагностическая попытка с сохранённым старым sandbox=true выявила `xcrun: error: cannot be used within an App Sandbox`; это тоже не текущая runtime-квалификация. + +## Артефакты и восстановление контекста + +Основной каталог: `/Users/admin/DuckDuckGo/apple-browsers.git/ci-validation-7562-7592-20261023`. + +- `head-12a187/preflight-summary.json` — пять loader-проверок текущего head. +- `head-12a187/state.json` — состояние до остановки; `attempt-1-state.json`, `qualify-attempt-1.py` и `controller.log` сохраняют исходную ошибку сценария. +- `readiness-summary.json` — native readiness 1/1. +- `results/critical-production/` — старые результаты, xcresults и логи; head указан в корневом `state.json`. +- `entitlements-diagnostic/` — отдельная неуспешная диагностическая попытка. +- `stopped-by-user.json` — снимок остановки и конечных статусов CI. +- Логи script tests: `/private/tmp/ddg-pr-stack-20261023/parent-script-tests.log` и `child-script-tests.log`. +- Screenshot Downloads prompt: `/private/tmp/ddg-printing-32-attachments/F3925F17-1A19-4743-8643-39F2E7DD5945.jpeg`. + +Пользователь разрешил одноразовый Sync account через официальный `duckduckgo/sync_crypto` с удалением после тестов, однако **аккаунт не создавался**, recovery code не передавался. Клон генератора собран в `sync-crypto/`; pin `b5a3ccb343817e592daaa0c3bb191fdcf731b9c8`. + +VM и рабочие деревья не удалялись; PR не закрывались и не сливались. Работы и мониторинг не возобновлять без нового прямого поручения пользователя. + +## Продолжение 23 октября 2026 года + +Пользователь разрешил продолжение кода, коммитов, push и CI. Обе ветки и опубликованные PR heads сверены с сохранёнными SHA: PR #7562 — `a5db06b1ad14f59ed41821b3ec9561670231486c`, PR #7592 — `12a187ff86d6599293f3061e125eed69e5355bf7`; оба PR остаются открытыми draft, рабочие деревья чистые. Старые UI/Sync/UI runs имеют окончательное состояние cancelled, Performance run — success. На локальном хосте не обнаружено контроллера квалификации этого стека; другие активные xcodebuild/SSH процессы относятся к иным задачам и не затронуты. + +Virfield подтвердил: lease Monterey `lease-376c79688b0798560d528772ecf89dd6` ready, истекает 24 октября 2026 в 10:58:20 Asia/Bishkek. Каталог прежнего SSH-ключа `/private/tmp/ddg-pr-stack-20261023/monterey-identity` пуст, поэтому доступ к этой VM из сохранённого сценария отсутствует. Попытка освобождения именно этого lease отклонена автоматической проверкой: удаление VM сочтено труднообратимым, продолжение тестов не признано явным разрешением на удаление. Освобождение и обход отказа не выполнялись. В момент проверки оба слота Tart заняты; второй lease относится к другой задаче. + +Запущены независимые native CI проверки PR #7562 на неизменённом head: [UI run 39945723701](https://github.com/duckduckgo/apple-browsers/actions/runs/39945723701) (macOS 14/15/27, DMG, production+internal) и [Sync run 39945746624](https://github.com/duckduckgo/apple-browsers/actions/runs/39945746624). Это только старт, не результат; фактические xcresult и конечные статусы ещё требуется проверить. + + +## CI переделанного минимального стека + +- [DMG 39949300020](https://github.com/duckduckgo/apple-browsers/actions/runs/39949300020): parent head `f85df79269ea67f5f13d776f1cc7d78bf2bc1069`; notarized app producer завершён success, тестовые producers macOS 14/15 работают, macOS 27 ожидает runner. На macOS 14 штатное разрешение пакетов прошло, началась test-only сборка. +- [App Store 39950239961](https://github.com/duckduckgo/apple-browsers/actions/runs/39950239961): тот же exact head, AppStoreSandboxUITests, обе user modes, три существующих OS; ещё работает app producer. +- Опубликованная metadata старого Xcode 16.2 артефакта подтверждает `DTXcodeBuild=16C5032a`, ожидаемый guard нового macOS 12 consumer. Это проверка формата/версии, не runtime нового head. +- Квитанция новой переделки: `ci-validation-7562-7592-20261023/rewrite-validation.json`. + + +Снимок 23 октября, 12:11 UTC (18:11 Asia/Bishkek): оба DMG producer PR1 (macOS 14/Xcode 16.2 и macOS 15/Xcode 26.2) завершились success. Скачанный новый архив macOS 14 проверен без запуска: ровно один xctestrun, относительный `UITargetAppPath=__TESTROOT__/Review/DuckDuckGo Review.app`, DTXcodeBuild=16C5032a, executable mode 0755 у test binary и tests-server, 21 symlink, браузер исключён. macOS 27 producer остаётся queued без назначенного runner; native class consumers ещё не начались из-за матричной зависимости. App Store приложение PR1 успешно собрано, его producers macOS 14/15 выполняются, 27 queued. Новый [child CI 39950846497](https://github.com/duckduckgo/apple-browsers/actions/runs/39950846497) на exact head `4283b2cd33ea34f4370799d5b0b94d0b0c502248` ещё выполняет universal app producer. Оба рабочих дерева чистые. Полный native и macOS 12 runtime pass пока не подтверждён. diff --git a/work/notes/work-summary-notes-draft-2026-10-23.md b/work/notes/work-summary-notes-draft-2026-10-23.md new file mode 100644 index 00000000..1a996781 --- /dev/null +++ b/work/notes/work-summary-notes-draft-2026-10-23.md @@ -0,0 +1,49 @@ +--- +tags: [work, performance-review, draft] +date: 2026-10-26 +status: draft-for-manual-paste +source: Asana Work Summary 1213731680648355 (copy fetched 2026-10-26, not modified since 2026-10-23), personal_os DB (read-only) +--- +**Structural work, refactoring and tooling** +- Feb/10: [New Type-Safe Settings Protocol: KeyValueStoring with StoringKeys](https://app.asana.com/0/0/1213211176970868) +- Mar/6: [Convert all macOS modules to Swift Package](https://app.asana.com/0/0/1211573210737831) – AppUpdater, CrashReporting and Bitwarden moved to local Swift packages +- Apr/28: [Proposal: Rollback /Applications/DEBUG install dir for macOS browser](https://app.asana.com/0/0/1214136461142650) +- Sep/30: [Proposal: Code-initialized CoreData models](https://app.asana.com/0/0/1218983598776255) +- Oct/16: [Migration to fully VSCode-supported builds&debugging](https://app.asana.com/0/0/1209808486380252) – closed Oct/16: full dev cycle runs from VS Code/Cursor; not adopted team-wide (opt-in setup); includes compile-flag removal, storyboard removal (12 PRs), #bundle localization, dead target flags, sandbox-test-tool replacement +- Oct/19: [Remove CoreData SPM test hacks compiling model in runtime](https://app.asana.com/0/0/1218987523298563) +- open: [Move macOS UI Tests target to separate xcode project file](https://app.asana.com/0/0/1219521748932290) + +**Tests and CI** +- Jul/16: [macOS: Only build arm64 architecture for UI Tests](https://app.asana.com/0/0/1216591205063619) +- Sep/29: [macOS 27: WebKit HUD private API has changed](https://app.asana.com/0/0/1218906032497990) +- Oct/21: [macOS: Address bar Ask AI Chat button keeps the closed window’s objects alive](https://app.asana.com/0/0/1219676439283927) +- Oct/21: [Swift Testing migration batch](https://app.asana.com/0/0/1219709925032923) +- Oct/21: [Three package test bundles never run on PR CI: EventHubTests, SnapshotTestingSupportTests, UIComp…](https://app.asana.com/0/0/1219728566677383) + +**Crashes and bug fixes** +- Feb/19: [Option menu not displaying with control + click](https://app.asana.com/0/0/1213223886344977) +- Mar/30: [Investigate and consider applying recent KSCrash changes](https://app.asana.com/0/0/1213875029587621) +- Apr/7: [Downloaded file names are cut short](https://app.asana.com/0/0/1213803861044638) +- Apr/21: [Unable to download from DMV site due to popup blocking.](https://app.asana.com/0/0/1213832268661772) +- Apr/23: [Crash in NSException+cxxHandler.mm](https://app.asana.com/0/0/1214143353966986) +- Jul/27: [Pinned tab opens link in background tab](https://app.asana.com/0/0/1214038885921128) +- Oct/16: [EXC_BAD_ACCESS NSError.domainAndCode.getter](https://app.asana.com/0/0/1219292410414766) +- Oct/20: [SIGSEGV/SIGKILL EmailKeychainManager.retrieveData (RecursiveLockWithSideEffects unbounded recursi…](https://app.asana.com/0/0/1215495893369499) + +**Other work** +- Feb/6: scoping [macOS: Address bar bugs batch](https://app.asana.com/0/0/1213148466589898) +- Feb/10: [announcement: New Type-Safe Settings Protocol](https://app.asana.com/0/1213210586557160/), [KeyValueStoring Adoption Guidelines](https://app.asana.com/0/0/1213193685927782) +- Jul/8: [Pixel anomaly report 2026.04.07 (mac) – Downloads assertion failure](https://app.asana.com/0/0/1213975525679527) – proactive improvement of downloads error handling +- Mar/31: [Using local LLM model (llama/qwen) for iTerm2 command generation](https://app.asana.com/0/0/1213885214199647) +- Jul/16: [macOS REVIEW builds now default to arm64-only](https://app.asana.com/0/1216597500971739/) +- Sep/3: [review comment finding an issue in a macOS PR and helping to fix it](https://github.com/duckduckgo/apple-browsers/pull/6584#pullrequestreview-5098464623) +- Oct/6: [Mapping Apple OS builds to WebKit source releases](https://app.asana.com/0/0/1219208775858201) (WebKit ↔ OS table for crash forensics, presented at Apple Weekly Oct/6) +- Oct/16: [How many users are converting from the half-sheet promo?](https://app.asana.com/0/0/1219469970318428) – my analysis comment for the subscriptions team +- Oct: [VSCode Setup for iOS/macOS development](https://app.asana.com/0/0/1209678170937083), [How to work with Apple Browsers using Git worktrees](https://app.asana.com/0/0/1219571548600350) + +**Apple Weekly topics** +- [Feb/10](https://app.asana.com/0/0/1213074285981095): type-safe settings protocol (KeyValueStoring) +- [Apr/21](https://app.asana.com/0/0/1213976243215792): proposal to roll back `/Applications/DEBUG` (feedback from Diego and Dominik changed the approach) and a demo of a VM-based UI test runner for agents +- [Sep/29](https://app.asana.com/0/0/1218750629653155): CoreData code-initialized models proposal (dropped after objections) +- [Oct/6](https://app.asana.com/0/0/1218994340026779): macOS modularization plan and the WebKit ↔ OS version table +- [Oct/20](https://app.asana.com/0/0/1219460858431911): UI tests target split, VS Code postmortem diff --git a/work/notes/work-summary-notes-meta-2026-10-23.md b/work/notes/work-summary-notes-meta-2026-10-23.md new file mode 100644 index 00000000..abde8618 --- /dev/null +++ b/work/notes/work-summary-notes-meta-2026-10-23.md @@ -0,0 +1,26 @@ +--- +tags: [work, performance-review, meta] +date: 2026-10-26 +--- + +# Служебные пояснения к Notes (не вставлять) + +(текст для вставки лежит в [[work-summary-notes-draft-2026-10-23]]) + +# Work Summary → Notes: всё, что не входит в основные секции (черновик 2026-10-26) + +[Work Summary](https://app.asana.com/0/0/1213731680648355). Правило отбора: задачи, назначенные на вас, закрытые 2026-02-01 → 2026-10-26 (плюс открытые с вашими комментариями), **которых нет в основных секциях Work Summary и которые не являются их подзадачами**. Подзадачи отдельно не перечислены нигде: у родителей (VS Code, epic Swift Package) их результат в одной строке. Всего ссылок в блоке: 98. + +Исключено: +- подзадачи задач из основных секций (120), сами задачи основных секций (33); +- процессные и личные задачи: 1 Must, 1:1 agenda, AFK/OOO, опросы, обучения, SWAG, benefits, review-процесс, кадровые (около 240); +- ротации (maintenance и release DRI) вместе со служебными подзадачами: ежедневные статусы, Validate/Merge/Announce/Submit/Promote, release exceptions, dependency-апдейты с Release Board. Ротации уже есть в основной секции Scheduled Rotations; +- служебные задачи проектов: Update O-N Weekly, Ship Review: Pull Request(s), Check Experiment, Missing info, diary-посты; +- удаление feature flags (9) и автотикеты «PR Check is failing on main» / «GH Workflow Failure»; +- приватные задачи (без проекта и без других участников, не видны остальным): 47 шт.; +- «PR:»-задачи-обёртки над PR (64): сами PR уже в списке Pull Requests; +- ⚠️ SENSITIVE DATA: Security. Задачи с названиями про security-инцидент (Security Triage: AI Agent use via likely external Discord service, его Postmortem и Full Analysis; Postmortem: Security Triage AI). Содержимое не открывал, не включал. + +Перед вставкой: Notes перезаписываются целиком; сверьте с `html_notes`. Группа «Closed Feb/11» — массовое закрытие старых задач: оставьте, только если это ваша работа цикла. + +--- diff --git a/work/projects/CPM Breakage Testing Steps.md b/work/projects/CPM Breakage Testing Steps.md index 51f72308..d77ed94d 100755 --- a/work/projects/CPM Breakage Testing Steps.md +++ b/work/projects/CPM Breakage Testing Steps.md @@ -1,8 +1,47 @@ --- -updated: '2026-10-20' +updated: '2026-10-30' --- # CPM episode telemetry — testing steps +## Error-6 restart — PR 7730 (30 October 2026) + +Current production PR: [#7730](https://github.com/duckduckgo/apple-browsers/pull/7730), **`cf668e2938`**, OPEN and not shipped. Review harness: `codex/cpm-error6-restart-harness` at **`5348c7254e`**, production code identical to the PR; its extra fixtures are outside main and production CI. Use a clean, isolated macOS checkout with the repository's Xcode toolchain. + +1. Fetch and select the harness: + ```sh + git fetch origin + git switch --detach origin/codex/cpm-error6-restart-harness + ``` +2. Disable only retained-view restart: + ```sh + CPM_REPRO=1 CPM_REPRO_RESTART=0 swift test \ + --package-path SharedPackages/WebExtensions \ + --filter CPMReproError6RestartTests/testRetainedError6RestartRestoresMessaging + ``` + Expected **functional FAIL**: `enabled=false error6=true retained=true`, original document timeout, nonempty `lost`, `restarts=0 responses=0`; existing M4 eventually repairs messaging. Setup errors, skips or absence of the injected real error are not valid negative controls. +3. Enable restart on the same binary: + ```sh + CPM_REPRO=1 CPM_REPRO_RESTART=1 swift test \ + --package-path SharedPackages/WebExtensions \ + --skip-build \ + --filter CPMReproError6RestartTests/testRetainedError6RestartRestoresMessaging + ``` + Expected **PASS**: `enabled=true error6=true retained=true`, original document gets an attributed dashboard response **and actual page initResp**, `lost=[] restarts=1 responses=1`, two later ordinary idle/wake cycles answer. +4. Return to production: + ```sh + git switch --detach origin/alex/cpm-error6-restart + ``` + +The last completed same-binary real-WebKit OFF/ON used **production `d1a2e1de65`**, not the latest head. The fixture branch is now synchronized for reviewers to rerun. This injected worker-start exception validates the retained-view mechanism; it does not establish the natural production trigger or physical iOS suspension behavior. + +Permanent tests in the PR cover new/historical/equal errors, healthy replacements, weak ownership, foreground/wake, runtime kill switches, queued newer errors, automatic cooldown expiry and expiry in background. Telemetry tests cover success vs background_ready, measured/unobserved deadlines, suspension, supersession, reload failure/timeout and embedded upgrades. Same-identifier loaded and another extension type must not supersede an active observation. These tests run in ordinary PR CI; the optional slow fault-injection harness does not. + +Latest local package result: **1064 PASS**; final source review with eight temporary probes: **1072 PASS**, no important findings. The temporary probes were removed. Treat `background_ready` only as a stage, not messaging success. Evaluate `web_extension_cpm_background_load_restart` using **count** rows; daily deduplication is by name. No response by deadline or a missing terminal row after process exit does not prove permanent breakage. + +Full contract and evidence: [[cpm-status-2026-10-15#Error-6 restart — PR 7730]]. + +## Historical episode-telemetry scope — 20 October 2026 + Verified against #7376 **`5d1cb4abd2`**, 20 October 2026. These expectations apply to that published branch; the PR is closed without merge. The macOS counterpart #7484 still has the earlier `was_stuck` contract. Full values and limitations: [[cpm-pr0-episode-telemetry-summary-2026-10-16]]. Use an isolated test profile and filter the Xcode console by `PixelKit` and `CPM Health Monitor`. Finish each episode before starting the next case. In production, the first completed episode per day sends `web_extension_cpm_episode_daily` and `_count` with the same payload; subsequent episodes send count only, across every reason/outcome. PixelKit dry-run logs instead of sending and compresses daily deduplication to two minutes, so a five-minute case may cross that boundary. diff --git a/work/projects/cpm-episode-telemetry-phase2-plan-2026-10-20.md b/work/projects/cpm-episode-telemetry-phase2-plan-2026-10-20.md index f46974a7..d04072bb 100644 --- a/work/projects/cpm-episode-telemetry-phase2-plan-2026-10-20.md +++ b/work/projects/cpm-episode-telemetry-phase2-plan-2026-10-20.md @@ -125,7 +125,7 @@ Outcome `no_response_before_deadline` is never proof of permanent failure. `relo - Эпизоды: доля `no_response_before_deadline` и `bg_events` с `died_client` после рестарта (неудачное восстановление); `reload_state` как запасная перезагрузка по зависанию. - Success-пиксели #7379 (`outcome=background_ready`) вместе с `recovery_applied`: резкий рост повторов после рестартов указывает на пункт 2. -**Отложенная правка** (комментарий 21 октября 2026): подписать рестарт #7459 на сброс состояния #7379 только для перезапущенного контекста. Лежит **незакоммиченной** в worktree `alex/cpm-fix-init-loss-production` (ветка `alex/cpm-fix-init-loss`) и `alex/cpm-fix-init-loss-macos` (ветка `alex/cpm-fix-init-loss-macos-release`); обе ветки уже смержены в релизы, патч рассчитан на текущие файлы. Оценка: польза узкая (работает, только если на момент рестарта старт ещё не завершён; обычно он уже завершён), побочные эффекты (ждущие навигации отпускаются раньше 5 с, теряется замер первой загрузки и install/upgrade для перезапущенной попытки). Ревью дважды (независимо): безопасно, крашей и рекурсии нет; нужно добавить тест менеджера (открыть `cpmNetworkProcessRecovery` internal) и записать ограничения в описание PR. Отдельно отвергнута первая версия, которая сбрасывала состояние на каждый возврат приложения: на iOS это каждое переключение приложений и сброс легитимного старта. +**Отложенная правка** (комментарий 21 октября 2026): подписать рестарт #7459 на сброс состояния #7379 только для перезапущенного контекста. Worktree удалены 21 октября 2026; полный патч (с тестами) сохранён в `~/DuckDuckGo/cpm-worktree-backups-20261021/alex_cpm-fix-init-loss-production.diff` (iOS) и `alex_cpm-fix-init-loss-macos.diff` (macOS); применяется поверх `release/ios/7.243.0` и `release/macos/1.213.0` через `git apply --3way`. Ветки `alex/cpm-fix-init-loss` и `alex/cpm-fix-init-loss-macos-release` смержены в релизы. Оценка: польза узкая (работает, только если на момент рестарта старт ещё не завершён; обычно он уже завершён), побочные эффекты (ждущие навигации отпускаются раньше 5 с, теряется замер первой загрузки и install/upgrade для перезапущенной попытки). Ревью дважды (независимо): безопасно, крашей и рекурсии нет; нужно добавить тест менеджера (открыть `cpmNetworkProcessRecovery` internal) и записать ограничения в описание PR. Отдельно отвергнута первая версия, которая сбрасывала состояние на каждый возврат приложения: на iOS это каждое переключение приложений и сброс легитимного старта. Ключевая часть патча (плюс два теста: «хук вызывается один раз с идентификатором» и «не вызывается при неудачном рестарте»): diff --git a/work/projects/cpm-extension-js-review-2026-10-30.md b/work/projects/cpm-extension-js-review-2026-10-30.md new file mode 100644 index 00000000..9a24fe7d --- /dev/null +++ b/work/projects/cpm-extension-js-review-2026-10-30.md @@ -0,0 +1,95 @@ +--- +tags: + - cpm + - ios + - investigation +updated: '2026-10-30' +status: verified-control-flow-findings +--- +# CPM extension: проверка JavaScript — 30 октября 2026 + +Да: отдельно проверены исходники самого extension и CPM bundle, извлечённый из iOS 7.243.0. Дополнение к [[cpm-ios-idle-wake-investigation-2026-10-30]]. + +Проверенный source checkout: `b00cc227112d4548755af9d3c4d93029c6e07066`, 15 октября, с init progress instrumentation. Это локальный checkout, не утверждение об актуальном upstream HEAD. Shipped app tag: `7.243.0+ios`, embedded manifest `2026.10.14`. Код приложения и extension не менялся. + +## 1. Запоздалый initResp не привязан к исходному документу + +Shipped handler сохраняет tabId/frameId и URL, затем ждёт config/settings/site policy. Ответ отправляется через `tabs.sendMessage(tabId, ..., {frameId})`; идентификатора документа или init request в payload нет. Content-script callback принимает любой `initResp` и вызывает initialize без проверки, какому init он принадлежал. + +Если пока handler A ждёт ответ на policy check, вкладка переходит на B, её main-frame по-прежнему адресуется как frameId=0. Старый handler не отменяется. WebKit tabsSendMessage выбирает текущее tab webView и процессы слушателей; исходный документ A не является целью вызова без document targeting. + +Контролируемая проверка: +- A отправляет init; её site check намеренно задержан. +- В той же вкладке создан B; для B site check возвращает false, dashboard получает site_disabled. +- После отпускания A старый initResp попадает в modeled current frame B. +- Реальный shipped receiveMessageCallback вызывает initialize(enabled=true) у B. +- При контрольном порядке A response → navigation B initialize происходит только у A. + +Это дефект корреляции протокола. В проверке используется модель navigation/frame routing и spy вместо DOM initialize; физический переход на iPhone в этом проходе не запускался. Из кода подтверждено отсутствие document guard. Доля production эпизодов и фактические действия CPM на странице B не измерены. + +Исправление: локальный per-document/per-init nonce, echo в ответе и проверка на стороне content script; отмена/игнорирование устаревших background задач. Сравнения только URL недостаточно при reload того же адреса. Поддерживаемый documentId может быть дополнительной защитой, но его совместимость следует проверить отдельно. + +Sources: +- [Shipped sendMessage](/Users/admin/DuckDuckGo/apple-browsers.git/cpm-ios-idle-wake-investigation-2026-10-30/shipped-extension/public/js/background-embedded.js:2715). +- [Shipped receiveMessageCallback](/Users/admin/DuckDuckGo/apple-browsers.git/cpm-ios-idle-wake-investigation-2026-10-30/shipped-extension/public/js/content-scripts/cpm.js:3333). +- [Локальный extension source](/Users/admin/DuckDuckGo/duckduckgo-privacy-extension/shared/js/background/components/cookie-prompt-management.js:640). + +## 2. Восстановление config не восстанавливает init текущего документа + +При remoteConfigJson=null handler вызывает scheduleConfigRefresh, отправляет dashboard config_unavailable и возвращается без initResp. Новый refresh не сохраняет/не повторяет исходный init. Shipped content script отправляет init в конструкторе один раз; recovery-retry этой инициализации не найден. + +Контролируемая проверка: +- config для первого init отсутствует; +- новый refresh сразу успешно восстанавливает config; +- после его завершения initResp по-прежнему не отправлен; +- явно повторённый init получает initResp. + +Это конкретный пробел восстановления после временной ошибки. Dashboard при этом может сообщить config_unavailable и закрыть health episode, хотя исходный документ остаётся без инициализации. Сам по себе этот сценарий не объясняет browser-wide пяти минутный no-response: новые navigation после восстановления config способны работать. + +В существующем unit-test проверен dashboard config_unavailable; восстановление исходного init этот тест не утверждает. + +Исправление: ограниченный retry pending init для ещё живого того же документа; обязательны document guard, отмена на navigation и idempotency, чтобы не повторять действия CPM. Нельзя сохранять pending init только по tabId/frameId. + +Source: [Shipped config-unavailable branch](/Users/admin/DuckDuckGo/apple-browsers.git/cpm-ios-idle-wake-investigation-2026-10-30/shipped-extension/public/js/background-embedded.js:2588). + +## 3. Storage ожидания могут глобально удерживать dashboard + +Подтверждённое ранее свойство: config get/set без собственного deadline и общая _stateQueue, которая ждёт session storage. Одна never-settled запись cpmState удерживает dashboard разных вкладок. Shipped probes дали две попытки отправить initResp и ноль dashboard-вызовов, пока запись не отпущена. + +Это не доказательство естественного зависания WebKit storage. Но это конкретная зависимость критичного наблюдения от persistence. Состояние JS event loop/runtime может оставаться работоспособным. Отдельный app↔page ACK нужен, чтобы отличить реальную потерю CPM от зависания dashboard pipeline. + +Исправление требует отделить оперативное in-memory состояние и delivery evidence от persistence, сохранив порядок/revision записей. Простой timeout не отменяет старую запись и может создать stale-write race. + +## Проверки и границы + +`extension-flow-probes.cjs` выполняет три сценария: config recovery без replay, ordered-navigation control, overlapping navigation. Они повторены на: +- shipped CookiePromptManagement + shipped receiveMessageCallback; +- классе CookiePromptManagement из локального extension source + том же shipped receiveMessageCallback. + +Оба набора дали одинаковые результаты. Source I/O замокан; реальные методы обработки сообщений и приёма initResp используются без изменения. Constructor/alarms/pixel emission не являются предметом этих проверок. Config/state probes предыдущего прохода сохранены отдельно. + +Результаты: `extension-flow-probe-results.json`, `extension-flow-checkout-results.json` в `/Users/admin/DuckDuckGo/apple-browsers.git/cpm-ios-idle-wake-investigation-2026-10-30/`. + +Приоритет для узкого extension fix: document-bound init/response и cancellation; затем bounded recovery исходного init после временной config ошибки. Параллельно измерить state queue, чтобы проверить её вклад в wake dashboard failures. Ни один из механизмов пока нельзя объявить единственной причиной всей production idle-wake группы. + +## Длительная поломка во всех вкладках — уточнение + +Общий pending remoteConfigJson способен без собственного дедлайна остановить init всех новых документов текущего worker. Контролируемая проверка shipped методов с удерживаемым config promise: три новые вкладки — 0 initResp и 0 dashboard; отдельный свежий экземпляр с доступным config работает, пока старый остаётся заблокирован. После освобождения старого promise завершаются все три init. Это доказательство зависимости/liveness при заданном stall, а не естественного WebKit trigger. + +Pending state write отличается: он блокирует общий dashboard pipeline, но initResp могут продолжать отправляться. Его нельзя автоматически называть фактическим отказом CPM на страницах. + +Оба дефекта per-document из предыдущих разделов способны оставлять конкретный документ без корректной инициализации до reload. Они сами по себе не доказывают поломку всех будущих документов. + +Проверен shipped M4 путь: reloadExtension успешно выгружает старый context и вызывает loader.loadWebExtension; makeContext создаёт новый WKWebExtensionContext. Чистая in-memory JS очередь/promise не должны пережить действительную замену worker. В проверенном upstream WebKit context unload вызывает invalidateStorage и сбрасывает sessionStorageStore; обычный idle unload background view этого не делает. Эти детали upstream не являются верификацией каждого shipped iOS engine. + +Поэтому для отказа свежих navigation после успешно выполненного M4 нужны либо повторение trigger в новом worker, либо общий нижележащий сбой, либо ошибка самого recovery/измерения. Подтверждённого чисто extension-JS состояния, которое навсегда ломает новые документы и гарантированно переживает успешную полную замену контекста, в этом проходе не найдено. session storage здесь не следует путать с постоянной дисковой записью поломки. + +Артефакты: global-config-probe.cjs / global-config-probe-results.json. Проверка не запускала реальный M4 на iPhone; fresh-instance control проверяет границу JS состояния. + +## Общая очередь: связь между вкладками и лишние записи report + +cpmState содержит dashboardStates по tabId, detectionCache, summaryEvents и globalErrors. Один экземпляр CookiePromptManagement обслуживает все вкладки. modifyCpmState добавляет операцию в один _stateQueue: getCpmState → callback изменения → await updateCpmState → await session.set. Ошибку/rejection цепочка ловит, после чего следующие операции могут идти; never-settled promise удерживает хвост. Это последовательная зависимость dashboard pipeline, не обязательная блокировка доставки initResp. + +Дополнительная проверка shipped handler: 64 сообщения report от subframes с пустым state вызвали 64 записи полного cpmState при неизменившемся состоянии. Причина: case report всегда вызывает modifyCpmState, хотя все изменения в callback защищены isMainFrame; завершающая запись выполняется и для no-op. Это проверенное усиление нагрузки и связь iframe-событий одной страницы с задержкой dashboard других вкладок. Задержка на реальном storage и production outage этой проверкой не измерены. Результат сохранён в subframe-report-probe-results.json. + +Другие общие места, совместимые с нормальной lifecycle-историей: pending remoteConfigJson; отсутствие RuntimeOnMessage route или отказ shared MessageRouter до autoconsent-handler; недоступный общий native bridge; повторяющийся JS exception на общем config/input; занятый SW JS thread; принятый native ответ, не прошедший current-document attribution. Для них нужны отдельные ingress/wait/rejection/ACK свидетельства. Нормальные bg_events описывают создание/удаление view и некоторые native/WebKit события, а не прохождение каждого из этих этапов. Их естественные production triggers пока не установлены. diff --git a/work/projects/cpm-failure-groups-2026-10-26_27.csv b/work/projects/cpm-failure-groups-2026-10-26_27.csv new file mode 100644 index 00000000..90bc9a86 --- /dev/null +++ b/work/projects/cpm-failure-groups-2026-10-26_27.csv @@ -0,0 +1,96 @@ +platform,group,event_pattern,users,pct_of_platform_failures,pct_of_dau,recovered,no_response_unconfirmed,permanent,pct_permanent,pct_of_all_permanent_on_platform +iOS,ALL,,29758,100.00,9.09,26388,1970,1400,4.70,100.00 +iOS,Error 6 (background failed to load),(group total),2821,9.48,0.86,2423,120,278,9.85,19.86 +iOS,Error 6 (background failed to load),[capped] interruption>view>proxy_on>dealloc>view>proxy_on>interruption>died_client>dealloc>view>proxy_on>error_wkwebextensioncontexterrordomain:6,129,0.43,0.04,119,6,4,3.10,0.29 +iOS,Error 6 (background failed to load),[capped] (dealloc>view>proxy_on)x2>interruption>died_client>dealloc>view>proxy_on>error_wkwebextensioncontexterrordomain:6,120,0.40,0.04,111,1,8,6.67,0.57 +iOS,Error 6 (background failed to load),[capped] view>proxy_on>interruption>dealloc>view>proxy_on>interruption>died_client>dealloc>view>proxy_on>error_wkwebextensioncontexterrordomain:6,112,0.38,0.03,111,0,1,0.89,0.07 +iOS,Error 6 (background failed to load),[capped] load>view>proxy_on>dealloc>view>proxy_on>interruption>died_client>dealloc>view>proxy_on>error_wkwebextensioncontexterrordomain:6,102,0.34,0.03,95,4,3,2.94,0.21 +iOS,Error 6 (background failed to load),load>view>proxy_on>dealloc>view>proxy_on>interruption>error_wkwebextensioncontexterrordomain:6,91,0.31,0.03,86,2,3,3.30,0.21 +iOS,Error 6 (background failed to load),(other 510 patterns),2267,7.62,0.69,1901,107,259,11.42,18.50 +iOS,No background view,(group total),5859,19.69,1.79,5541,265,53,0.90,3.79 +iOS,No background view,load,4979,16.73,1.52,4781,182,16,0.32,1.14 +iOS,No background view,[capped] view>proxy_on>(dealloc>view>proxy_on)x3>dealloc,243,0.82,0.07,204,34,5,2.06,0.36 +iOS,No background view,load>view>proxy_on>dealloc,56,0.19,0.02,54,0,2,3.57,0.14 +iOS,No background view,load>interruption,50,0.17,0.02,47,1,2,4.00,0.14 +iOS,No background view,load>view>proxy_on>dealloc>view>proxy_on>dealloc,39,0.13,0.01,34,3,2,5.13,0.14 +iOS,No background view,(other 146 patterns),492,1.65,0.15,421,45,26,5.28,1.86 +iOS,Tab not connected (P6),(group total),8,0.03,0.00,7,0,1,12.50,0.07 +iOS,Tab not connected (P6),load>view>proxy_on,8,0.03,0.00,7,0,1,12.50,0.07 +iOS,Extension updated <=2 min,(group total),1411,4.74,0.43,977,249,185,13.11,13.21 +iOS,Extension updated <=2 min,load>view>proxy_on>extension_updated,1122,3.77,0.34,744,210,168,14.97,12.00 +iOS,Extension updated <=2 min,load>extension_updated>view>proxy_on,111,0.37,0.03,92,19,0,0.00,0.00 +iOS,Extension updated <=2 min,load>view>proxy_on>extension_updated>dealloc>view>proxy_on,49,0.16,0.01,37,11,1,2.04,0.07 +iOS,Extension updated <=2 min,load>view>proxy_on>extension_updated>interruption,43,0.14,0.01,34,3,6,13.95,0.43 +iOS,Extension updated <=2 min,extension_updated>load>base_url_changed>view>proxy_on,37,0.12,0.01,29,0,8,21.62,0.57 +iOS,Extension updated <=2 min,(other 17 patterns),49,0.16,0.01,41,6,2,4.08,0.14 +iOS,Idle wake (fresh background <30 s),(group total),6219,20.90,1.90,5479,517,223,3.59,15.93 +iOS,Idle wake (fresh background <30 s),load>view>proxy_on>dealloc>view>proxy_on,693,2.33,0.21,633,53,7,1.01,0.50 +iOS,Idle wake (fresh background <30 s),[capped] (dealloc>view>proxy_on)x4,530,1.78,0.16,430,78,22,4.15,1.57 +iOS,Idle wake (fresh background <30 s),load>view>proxy_on>dealloc>interruption>view>proxy_on,331,1.11,0.10,312,12,7,2.11,0.50 +iOS,Idle wake (fresh background <30 s),load>view>proxy_on>dealloc>view>proxy_on>interruption,310,1.04,0.09,290,12,8,2.58,0.57 +iOS,Idle wake (fresh background <30 s),load>view>proxy_on>(dealloc>view>proxy_on)x2,259,0.87,0.08,234,21,4,1.54,0.29 +iOS,Idle wake (fresh background <30 s),(other 329 patterns),4096,13.76,1.25,3580,341,175,4.27,12.50 +iOS,Recent context load <=2 min,(group total),6725,22.60,2.05,5687,480,558,8.30,39.86 +iOS,Recent context load <=2 min,load>view>proxy_on,5122,17.21,1.56,4334,413,375,7.32,26.79 +iOS,Recent context load <=2 min,load>base_url_changed>view>proxy_on,689,2.32,0.21,564,8,117,16.98,8.36 +iOS,Recent context load <=2 min,load>view>proxy_on>interruption,462,1.55,0.14,401,34,27,5.84,1.93 +iOS,Recent context load <=2 min,load>view>proxy_on>dealloc>view>proxy_on,163,0.55,0.05,147,13,3,1.84,0.21 +iOS,Recent context load <=2 min,extension_updated>load>base_url_changed>view>proxy_on,89,0.30,0.03,62,3,24,26.97,1.71 +iOS,Recent context load <=2 min,(other 22 patterns),200,0.67,0.06,179,9,12,6.00,0.86 +iOS,Other,(group total),6715,22.57,2.05,6274,339,102,1.52,7.29 +iOS,Other,[capped] (dealloc>view>proxy_on)x4,800,2.69,0.24,741,50,9,1.12,0.64 +iOS,Other,load>view>proxy_on,760,2.55,0.23,720,36,4,0.53,0.29 +iOS,Other,load>view>proxy_on>dealloc>view>proxy_on,631,2.12,0.19,582,44,5,0.79,0.36 +iOS,Other,load>view>proxy_on>interruption,374,1.26,0.11,361,13,0,0.00,0.00 +iOS,Other,load>view>proxy_on>(dealloc>view>proxy_on)x2,243,0.82,0.07,226,14,3,1.23,0.21 +iOS,Other,(other 268 patterns),3907,13.13,1.19,3644,182,81,2.07,5.79 +macOS,ALL,,5946,100.00,28.54,3742,1606,598,10.06,100.00 +macOS,Error 6 (background failed to load),(group total),28,0.47,0.13,17,8,3,10.71,0.50 +macOS,Error 6 (background failed to load),load>view>proxy_on>dealloc>view>proxy_on>error_wkwebextensioncontexterrordomain:6,5,0.08,0.02,1,2,2,40.00,0.33 +macOS,Error 6 (background failed to load),[capped] view>proxy_on>(dealloc>view>proxy_on)x3>error_wkwebextensioncontexterrordomain:6,3,0.05,0.01,3,0,0,0.00,0.00 +macOS,Error 6 (background failed to load),load>view>proxy_on>extension_updated>dealloc>error_wkwebextensioncontexterrordomain:6>view>proxy_on,2,0.03,0.01,0,2,0,0.00,0.00 +macOS,Error 6 (background failed to load),load>view>proxy_on>extension_updated>died_client>dealloc>view>proxy_on>error_wkwebextensioncontexterrordomain:6,2,0.03,0.01,2,0,0,0.00,0.00 +macOS,Error 6 (background failed to load),[capped] load>view>proxy_on>extension_updated>dealloc>view>proxy_on>interruption>dealloc>view>proxy_on>error_wkwebextensioncontexterrordomain:6,1,0.02,0.00,1,0,0,0.00,0.00 +macOS,Error 6 (background failed to load),(other 15 patterns),15,0.25,0.07,10,4,1,6.67,0.17 +macOS,No background view,(group total),239,4.02,1.15,161,58,20,8.37,3.34 +macOS,No background view,load,54,0.91,0.26,47,4,3,5.56,0.50 +macOS,No background view,[capped] view>proxy_on>(dealloc>view>proxy_on)x3>dealloc,50,0.84,0.24,35,12,3,6.00,0.50 +macOS,No background view,load>view>proxy_on>dealloc,19,0.32,0.09,11,6,2,10.53,0.33 +macOS,No background view,[capped] proxy_on>(dealloc>view>proxy_on)x3>dealloc>interruption,12,0.20,0.06,6,5,1,8.33,0.17 +macOS,No background view,[capped] proxy_on>(dealloc>view>proxy_on)x2>dealloc>interruption>view>proxy_on>dealloc,8,0.13,0.04,5,3,0,0.00,0.00 +macOS,No background view,(other 45 patterns),96,1.61,0.46,57,28,11,11.46,1.84 +macOS,Tab not connected (P6),(group total),333,5.60,1.60,149,53,131,39.34,21.91 +macOS,Tab not connected (P6),load>view>proxy_on,167,2.81,0.80,78,25,64,38.32,10.70 +macOS,Tab not connected (P6),load>view>proxy_on>extension_updated,72,1.21,0.35,34,15,23,31.94,3.85 +macOS,Tab not connected (P6),load>extension_updated>view>proxy_on,37,0.62,0.18,14,4,19,51.35,3.18 +macOS,Tab not connected (P6),load>view>proxy_on>dealloc>view>proxy_on,22,0.37,0.11,11,2,9,40.91,1.51 +macOS,Tab not connected (P6),load>view>proxy_on>extension_updated>dealloc>view>proxy_on,15,0.25,0.07,4,4,7,46.67,1.17 +macOS,Tab not connected (P6),(other 13 patterns),20,0.34,0.10,8,3,9,45.00,1.51 +macOS,Extension updated <=2 min,(group total),1263,21.24,6.06,822,351,90,7.13,15.05 +macOS,Extension updated <=2 min,load>view>proxy_on>extension_updated,804,13.52,3.86,548,212,44,5.47,7.36 +macOS,Extension updated <=2 min,load>extension_updated>view>proxy_on,257,4.32,1.23,159,72,26,10.12,4.35 +macOS,Extension updated <=2 min,load>view>proxy_on>extension_updated>dealloc>view>proxy_on,109,1.83,0.52,59,42,8,7.34,1.34 +macOS,Extension updated <=2 min,load>extension_updated>view>proxy_on>dealloc>view>proxy_on,39,0.66,0.19,19,15,5,12.82,0.84 +macOS,Extension updated <=2 min,load>extension_updated>view>proxy_on>died_client>dealloc>view>proxy_on,15,0.25,0.07,8,4,3,20.00,0.50 +macOS,Extension updated <=2 min,(other 9 patterns),39,0.66,0.19,29,6,4,10.26,0.67 +macOS,Idle wake (fresh background <30 s),(group total),2181,36.68,10.47,1258,696,227,10.41,37.96 +macOS,Idle wake (fresh background <30 s),[capped] (dealloc>view>proxy_on)x4,942,15.84,4.52,514,333,95,10.08,15.89 +macOS,Idle wake (fresh background <30 s),load>view>proxy_on>dealloc>view>proxy_on,160,2.69,0.77,105,40,15,9.38,2.51 +macOS,Idle wake (fresh background <30 s),[capped] view>proxy_on>(dealloc>view>proxy_on)x2>dealloc>interruption>view>proxy_on,158,2.66,0.76,98,46,14,8.86,2.34 +macOS,Idle wake (fresh background <30 s),load>view>proxy_on>dealloc>view>proxy_on>died_client>dealloc>view>proxy_on,79,1.33,0.38,55,11,13,16.46,2.17 +macOS,Idle wake (fresh background <30 s),load>view>proxy_on>(dealloc>view>proxy_on)x2,69,1.16,0.33,36,25,8,11.59,1.34 +macOS,Idle wake (fresh background <30 s),(other 73 patterns),773,13.00,3.71,450,241,82,10.61,13.71 +macOS,Recent context load <=2 min,(group total),720,12.11,3.46,514,162,44,6.11,7.36 +macOS,Recent context load <=2 min,load>view>proxy_on,639,10.75,3.07,457,143,39,6.10,6.52 +macOS,Recent context load <=2 min,load>base_url_changed>view>proxy_on,28,0.47,0.13,22,5,1,3.57,0.17 +macOS,Recent context load <=2 min,load>view>proxy_on>dealloc>view>proxy_on,24,0.40,0.12,15,8,1,4.17,0.17 +macOS,Recent context load <=2 min,extension_updated>load>base_url_changed>view>proxy_on,18,0.30,0.09,12,4,2,11.11,0.33 +macOS,Recent context load <=2 min,load>base_url_changed>view>proxy_on>dealloc>view>proxy_on,3,0.05,0.01,2,1,0,0.00,0.00 +macOS,Recent context load <=2 min,(other 7 patterns),8,0.13,0.04,6,1,1,12.50,0.17 +macOS,Other,(group total),1182,19.88,5.67,821,278,83,7.02,13.88 +macOS,Other,[capped] (dealloc>view>proxy_on)x4,566,9.52,2.72,402,131,33,5.83,5.52 +macOS,Other,load>view>proxy_on>dealloc>view>proxy_on,79,1.33,0.38,55,19,5,6.33,0.84 +macOS,Other,[capped] view>proxy_on>(dealloc>view>proxy_on)x2>dealloc>interruption>view>proxy_on,60,1.01,0.29,32,19,9,15.00,1.51 +macOS,Other,load>view>proxy_on>extension_updated>dealloc>view>proxy_on,59,0.99,0.28,43,12,4,6.78,0.67 +macOS,Other,load>view>proxy_on>(dealloc>view>proxy_on)x2,42,0.71,0.20,31,9,2,4.76,0.33 +macOS,Other,(other 66 patterns),376,6.32,1.80,258,88,30,7.98,5.02 diff --git a/work/projects/cpm-ios-idle-wake-investigation-2026-10-30.md b/work/projects/cpm-ios-idle-wake-investigation-2026-10-30.md new file mode 100644 index 00000000..b54a3f91 --- /dev/null +++ b/work/projects/cpm-ios-idle-wake-investigation-2026-10-30.md @@ -0,0 +1,222 @@ +--- +tags: + - cpm + - ios + - webkit + - investigation +updated: '2026-10-30' +status: investigation-and-proposal +--- +# CPM iOS idle wake: действия, возможные механизмы и диагностический план + +Продолжение с проверенными JavaScript findings: [[cpm-extension-js-review-2026-10-30]] — stale initResp пересекает navigation; config refresh не повторяет исходный init. Есть контролируемые проверки shipped bundle и локального extension source, с указанными границами. +Дата: 30 октября 2026. Продолжение [[cpm-ios-uncovered-groups-2026-10-30]]. + +**Основной вывод:** idle wake остаётся приоритетным сценарием, но текущий пиксель объединяет разные механизмы. Нужно разделить выполнение content script, доставку init в worker, ожидания внутри worker, dashboard → native → monitor и реальное получение initResp страницей. Проверка shipped bundle выявила две неограниченные storage-зависимости; контролируемые проверки показали, что общая очередь cpmState может остановить dashboard-ответы нескольких вкладок при продолжении отправок initResp. Естественный production-trigger такого storage stall пока не найден. + +## Что проверено и чего эти результаты не доказывают + +Проверены исходники **реально измеряемого iOS 7.243.0**, tag `7.243.0+ios`, commit `9b67ed7c720d274a554a86052881d2d690cccb3e`; ZIP extension извлечён из этого tag. Его manifest version — **2026.10.14**. Это важнее версии локального extension checkout: там уже есть ещё не выпущенные init progress marks, в shipped ZIP их нет. + +Дополнительно: текущий main `3934949758`; WebKit source snapshot `99051d5d08cd9f19ec76ce599f7539d070b1ae09` от 29 сентября; проверены upstream patches четырёх связанных WebKit bugs. **Наличие исправления в upstream main не устанавливает, в какой iOS версии оно включено.** Этот проход не создавал production PR и не менял приложение/флаги. + +Перечитаны записи 27–30 октября. В частности, гипотеза неограниченного `storage.session.get("config")` уже была записана 27 октября; это не впервые обнаруженная идея. Новое здесь — разбор общей state queue и контролируемое разделение dashboard и initResp на shipped методах. + +В этом проходе выполнены три изолированные проверки JS-логики с подменёнными browser/native I/O. **Новый естественный iOS/WebKit outage не воспроизведён**, физический iPhone в этом проходе не использовался. Предыдущие H1/H2, idle и real-page результаты учитываются со своими ограничениями, а не переобъявляются новыми. + +## 1. Какие действия пользователя дают эту картину + +### A. Достаточно читать страницу в открытом браузере, затем перейти по ссылке + +1. Открыть обычную HTTPS-страницу, получить работающий CPM. +2. Читать/прокручивать её, не создавая новых extension-событий. Примерно через 30 секунд последней активности WebKit может выгрузить непостоянный background. Это нормальный lifecycle, не crash. +3. Нажать ссылку, обновить страницу или открыть новую вкладку с URL. +4. На main-frame HTTP(S) navigation app запрашивает готовность extension **на policy stage**, до загрузки документа. WebKit создаёт новый background view, recorder устанавливает proxy. +5. Получаем `dealloc → view → proxy_on`; фон может успешно загрузиться за <1 секунды. +6. Документ выполняет CPM content script, отправляет init. Если требуемый dashboard-сигнал не дошёл до monitor за 4 секунды после finish, появляется episode и первый snapshot. + +Поэтому `view_age=4s_to_8s` естественен: время на загрузку страницы плюс четырёхсекундное ожидание. Он не доказывает, что view появился в момент сбоя. Несколько `dealloc>view>proxy_on` подряд могут быть обычными переходами с интервалами >30 секунд. + +Если страница регулярно шлёт extension-сообщения или открыт активный port, выгрузка может откладываться; ровно 30 секунд от открытия вкладки не является гарантией. + +### B. Home / блокировка телефона / возвращение в браузер + +1. Открыть 2–3 здоровые вкладки, перейти в другое приложение или заблокировать экран. +2. Во время простоя background может выгрузиться; native callbacks/timers могут отработать после resume. +3. Вернуться, затем **сделать новую navigation**: ссылка, reload, новый URL в другой вкладке. Получается та же wake-история, иногда с `interruption`. +4. Для подтверждённого cross-tab случая после первого timeout сделать свежую обычную navigation во второй вкладке; один только возврат к старому документу этого не доказывает. + +В shipped monitor resume **перезапускает уже ожидающие замеры**, обычно с 8-секундным grace; он не создаёт новый init/замер на любой показ старой страницы. Чистый back/forward restore исключён из health measurement. Поэтому «разблокировать и посмотреть старую страницу» и «разблокировать и перейти на новую страницу» — разные опыты. + +`interruption` в bg_events говорит о событии в доступной истории; `interrupted=true` означает, что именно первый измеряемый запрос пересёк suspension. Их нельзя смешивать. + +### C. Граница unload/wake и две почти одновременные navigation + +После 25–35 секунд idle начать переход в A и сразу в B; отдельно — заблокировать телефон во время перехода и завершить после resume. Навигации разделяют startup gate, но документы и сообщения имеют разные поколения. Это сильный сценарий для routing/registration и attribution races. + +Чтобы доказать browser-wide failure, после первоначального сбоя нужна ещё одна navigation, начатая уже после него; один пакет одновременно восстановленных вкладок для этого недостаточен. + +### D. M4 после второго timeout, затем тишина + +Второй квалифицирующий timeout запускает context reload. Пользователь может после этого просто остаться на странице и перестать навигировать. В конце активного окна получается no-response, хотя **проверки нового контекста свежим документом могло не быть**. + +Это существенная граница интерпретации прежних чисел. У wake cross-tab no-response 29 октября: +- `reload_state=loaded`: **2 140 из 2 920** count-эпизодов. +- `response_timed_out`: **776**. +- failed/pending: 4. + +`loaded` доказывает успешный context reload, но не проведённый и проваленный post-reload messaging test. `response_timed_out` сильнее: monitor зарезервировал post-reload regular measurement, и он истёк. Даже он не доказывает вечную поломку. + +Следовательно, фразу «все 2 920 пережили M4 без восстановления» применять нельзя. Для опытов после M4 обязательно отдельно открыть новый документ и проверить actual initResp. + +### E. Похожий footprint может создать не поломка worker + +Валидная страница после idle может: +- не выполнить ожидаемый content script; +- выполнить init, но застрять на config/storage; +- получить initResp, пока dashboard ждёт сохранения state; +- прислать dashboard, который не прошёл parsing/маршрутизацию/атрибуцию. + +Все эти пути способны дать готовый background и одинаковые native flags. Эти флаги проверяют конфигурацию и WebKit verdict, а не прохождение конкретного JS-запроса. + +## 2. Реальная цепочка в shipped коде + +`navigation policy → native background gate → WebKit background load → content script → runtime.sendMessage(init) → MessageRouter → handleAutoConsentMessage → config/settings/site checks → [dashboard branch || initResp branch]`. + +**Ветки после init различаются:** +- Dashboard: `updateCpmDashboardState → modifyCpmState → storage.session → refreshCpmDashboardState native message → validation → health monitor correlation`. +- Ответ странице: `tabs.sendMessage(initResp) → content-script listener → AutoConsent.initialize`. + +Dashboard запускается через promise chain и **не является ACK доставки initResp**. Возможны оба расхождения: dashboard есть, actual initResp потерян; actual initResp отправляется/доставляется, dashboard отсутствует. + +В 7.243 MessageRouter завершает autoconsent-handler через `.then(() => {})`: успешный `runtime.sendMessage(init)` сам по себе может вернуть **undefined**. У нас undefined нельзя считать признаком потерянного worker, даже если в чужом ping/pong reproduction это аномалия. Нужны явные stages/ACK. + +Полезные source anchors: +- [App navigation gate](/Users/admin/DuckDuckGo/apple-browsers.git/cpm-ios-idle-wake-investigation-2026-10-30/shipped-app/TabViewController.swift:3662). +- [Shipped config/state/init](/Users/admin/DuckDuckGo/apple-browsers.git/cpm-ios-idle-wake-investigation-2026-10-30/shipped-extension/public/js/background-embedded.js:2570). +- [Независимая ветка dashboard](/Users/admin/DuckDuckGo/apple-browsers.git/cpm-ios-idle-wake-investigation-2026-10-30/shipped-extension/public/js/background-embedded.js:2678). +- [Content script ждёт отдельный initResp](/Users/admin/DuckDuckGo/apple-browsers.git/cpm-ios-idle-wake-investigation-2026-10-30/shipped-extension/public/js/content-scripts/cpm.js:2846). + +## 3. Конкретные механизмы и места поиска + +### H1. Unbounded config/storage и общий state queue — проверяемая app/extension-гипотеза + +В `refreshRemoteConfig` первый `getFromSessionStorage("config")` выполняется **перед** native request и его timeout. После успешного обновления config запись `storage.session.set` также ожидается без своего timeout. Все init ждут общий `remoteConfigJson`. + +Отдельно `modifyCpmState` сериализует обновления разных вкладок через `_stateQueue`. Операция ожидает `getCpmState`, затем `updateCpmState`, включая storage write. Rejection ловится и позволяет очереди продолжить; never-settled promise удерживает всё последующее. Dashboard каждого init зависит от этой очереди. Это глобальная точка блокировки, даже если JS event loop и browser.runtime работают. + +Проверки на извлечённых shipped классах: +1. При удерживаемом config read native request вообще не начался; его 20-секундный timeout ещё не мог помочь. После отпускания storage handler завершился. +2. Healthy control: два init дали две попытки initResp и два dashboard-вызова. +3. Pending cpmState write: два init дали **две попытки initResp**, **ноль dashboard-вызовов**, выполнена только первая заблокированная запись. После отпускания записи дошли оба dashboard-вызова. + +Это **логическая демонстрация механизма**, не доказательство естественного зависания browser.storage, доставки initResp в настоящий документ или доли production false positives. Browser/native I/O подменены управляемыми promises. + +Свежий JS realm после M4 должен очистить чисто in-memory очередь. Поэтому для повторения после M4 нужны повторный storage stall/общая нижележащая проблема либо другой механизм. Не всякий пяти минутный episode доказывает, что очередь оставалась зависшей после reload. + +Куда смотреть: config storage get/set; cpmState get/set; возраст головы очереди; глубина очереди; отдельный health/ACK путь. При будущем исправлении persistence не должна безгранично удерживать ответ/наблюдение. Одного `Promise.race(timeout)` недостаточно для безопасных записей: старая незавершённая запись может позднее завершиться и затереть более новую; нужны revision/order/coalescing semantics. + +### H2. Worker готов по load callback, но init не маршрутизируется в listener + +WebKit `runtimeSendMessage` после wake получает набор процессов для `RuntimeOnMessage`; если он пуст, завершает вызов пустым результатом. Выборка идёт по listener frames/content world, а не просто по живому PID background view. + +`bg_process_responsive` проверяет WebKit verdict о процессе WKWebView. Он не проверяет, что нужный SW listener зарегистрирован и выполняется. Carrier page, SW thread, API bindings и listener routing — разные уровни. + +Нужно поймать: content script точно начал init; worker ingress отсутствует при известной диагностической поддержке; альтернативный worker/native путь проверен. Одно отсутствие native progress mark недостаточно — сам мост может не работать. + +Предыдущие проверки H1 Network crash и H2 skip-listener из заметок **не подтвердили естественный persistent trigger** на production 30-секундном unload. Быстрый TestWebKitAPI режим давал артефакты; его красный результат не переносится на production без повторения. + +### H3. Init дошёл, но native bridge / handler / dashboard correlation потеряли результат + +Handler registered проверяет наличие native handler в словаре. Это не факт получения текущего сообщения. После message delivery есть отдельные parsing, URL/consentStatus validation и correlation по tab, document URL и extension generation. + +Monitor сравнивает scheme/host/port/path/query, игнорируя fragment. Bundle берёт tab URL из `sender.tab.url/pendingUrl`; при redirects, URL rewrite и пересечении context reload возможны несовпадения. При известных mismatch до failure monitor часто отменяет недоказуемый замер, поэтому нельзя любой mismatch объявить false-positive. Но после первого failure отсутствие подходящего current-generation response может оставить episode открытым. + +Нужны отдельные счётчики handler-entry, parse rejection, stale context, URL mismatch, zero candidates, ambiguous candidates и accepted attribution. Без них полученный, но отвергнутый ответ внешне равен отсутствующему. + +### H4. Script не выполнялся / ранний JS exception / неправильный sender + +`tab_has_ext_scripts=true` означает зарегистрированный user script нужного content world, а не исполнение в конкретном документе. Перед обработкой init bundle проверяет sender.tab, frameId и tabId; часть неверных метаданных ведёт к раннему return. + +Искать после independent cs-start evidence. Исследовать обычную navigation, восстановление/переиспользование web view, process swap и previews отдельно. Большая часть P6/controller mismatch к iOS wake-группе не относится. + +### H5. Idle wake может быть обычным состоянием большинства navigation + +Большая доля wake среди failures не определяет риск wake. После 30-секундной выгрузки многие здоровые navigation тоже создают новый background. + +Нужен знаменатель: `failed wake / все eligible wake navigation` против `failed warm / все eligible warm navigation`, одинаковая выборка и правила. Без него keep-warm может лечить действительно важный lifecycle race или просто сдвигать классификацию. До такого сравнения blanket keep-warm не является первым выбором. + +## 4. Что найдено в WebKit и публичных обсуждениях + +- [317981](https://bugs.webkit.org/show_bug.cgi?id=317981): потеря sendMessage на первоначальном старте при ещё неизвестном listener set; исправление добавляет очередь для initial load. **Первый load после очистки listener state — его scope**, обычный повторный wake он полностью не объясняет. +- [324743](https://bugs.webkit.org/show_bug.cgi?id=324743): readiness объявлялась до завершения registration; исправлено ожидание нужного состояния. Это показывает ограниченность прежнего loader-ready как messaging-ready, но не устанавливает наличие бага на нашей OS. +- [324754](https://bugs.webkit.org/show_bug.cgi?id=324754), [patch](https://github.com/WebKit/WebKit/commit/001f7dbb8bbf): после unload/reload worker мог оказаться в процессе, где отсутствует carrier page и не инжектируются extension bindings. Хорошее структурное сходство, однако требуется совпадение trigger/OS; прежняя наша Network-проверка это не подтвердила. +- [322881 / patch](https://github.com/WebKit/WebKit/commit/4ce58a7ff78ce27cf6646691c6ddfb966b22b51c): восстановление SW clients при повторном background load. Upstream regression проверяет offscreen client; обычные HTTPS-вкладки с content scripts не следует автоматически считать теми же SW clients. +- [Публичный iPhone reproduction](https://github.com/Happygallo/SafariExtensionBackgroundUndefinedBug): автор сообщает о сбое после lock/inactivity на iOS 18/26 и отсутствии проявления в Simulator/с Web Inspector. Исходники проверены: worker возвращает явный pong; manifest использует module worker, content script document_end и ping раз в секунду. У нас classic worker/document_start, явный native pre-wake и отдельный initResp. Это мотивирует device/без-inspector опыт, а не доказывает одинаковую причину. +- [Apple Forums 758346](https://developer.apple.com/forums/thread/758346): исторические device-only wake failures; автор описывает улучшение при непостоянной background page вместо service worker. Apple сообщала о фиксе в 17.6.1, позже есть сообщения пользователей о сохранении проблемы. Это не единый доказанный баг всех новых OS. +- [WWDC26](https://developer.apple.com/videos/play/wwdc2026/216/) описывает поддержку background page и service worker. Локальный WebKit manifest parser тоже поддерживает непостоянный document background при MV3. Поэтому **SW ↔ nonpersistent background page — допустимый диагностический A/B-кандидат**, а не keep-alive workaround. + +Наличие этих upstream fixes не означает, что надо ждать очередного Safari release. Сначала нужны наши stages и совпадающее воспроизведение. Safari-hosted extension и WKWebExtension внутри DDG отличаются управлением и lifecycle. + +## 5. Телеметрия, которая действительно различит эти ветки + +Это предложение контракта, не добавленные пиксели. Значения — ограниченные enum/bool/buckets; URL, tab IDs, document IDs, worker IDs и nonce в аналитику не отправляются. + +### Минимальный приоритетный набор + +**A. Per-document trace с локальной корреляцией.** Временный navigation/document token + extension generation только в памяти app/JS. Token проходит вместе с init/ответом, не сохраняется между сессиями и не уходит в ClickHouse. Он должен различать повторную загрузку того же URL и поколения после M4. Capability handshake: `unsupported/unknown/supported`; отсутствие marks у старого bundle/старого документа нельзя писать как `init_not_received`. + +**B. Раздельные положительные свидетельства на границах:** +- content script started; +- init send attempted / rejected / settled; +- worker init ingress; +- dashboard handler entry / validation / attribution; +- initResp send attempt / transport completion; +- content script actually received initResp и завершил предусмотренную initialize-стадию. + +Нельзя заменить последнюю точку успешным `tabs.sendMessage`: это транспортный результат, не прикладной ACK. И нельзя считать успешный runtime promise с undefined ACK принятия init. + +**C. Где ждёт worker.** Отдельные bounded markers: +`config_session_get`, `native_config_request`, `config_session_set`, `user_settings`, `site_policy`, `state_queue_wait`, `state_session_get`, `state_session_set`, `dashboard_notify`; возраст текущего ожидания и глубина очереди в buckets. Маркеры пишутся **до await**, collector не зависит от той же state queue/storage. Для параллельных dashboard/initResp нужны отдельные bits/statuses, а не один максимальный stage, который теряет информацию о второй ветке. + +**D. Native rejection reason.** `no_handler / malformed / stale_context / url_mismatch / no_candidate / ambiguous / attributed`, с отдельным `unknown` если входящий вызов не наблюдался. В аналитику идёт классификация расхождения, не адрес страницы. + +**E. First и end evidence + recovery opportunity.** Сохранить первый snapshot; дополнить bounded end-state и событиями, произошедшими после него: +- view/context replaced, background ready/failure; +- retry/M4 attempted либо suppression reason; +- количество eligible regular measurements после recovery: 0 / 1 / 2+; +- post-recovery timed out / dashboard attributed / page ACK / no opportunity; +- initial-document recovery и ответ другой вкладки отдельно. + +Пятиминутное отсутствие ответа при нуле post-reload opportunities не должно означать доказанный провал recovery. + +### Два обязательных условия дизайна + +**Независимость диагностического канала.** Если все worker marks идут только через потенциально сломанный native messaging, отсутствие marks не различает «init не пришёл» и «worker не может отчитаться». Нужен дополнительный app↔content-script diagnostic bridge с проверенной привязкой content world и/или post-failure диагностический runtime reply, который content script передаст native независимо. App-owned probe в другом world доказывает работу того probe, а не исполнение CPM script; эти результаты нельзя объединять. + +Positive marker надёжно показывает пройденную границу. Negative marker требует поддержки контракта и проверки канала; иначе результат остаётся unknown. Стопроцентно назвать внутренний баг WebKit только этими полями нельзя, но можно выбрать узкий участок для engine trace. + +**Healthy baseline и отсутствие изменения поведения до failure.** Те же bounded признаки для небольшой случайной выборки здоровых eligible navigation: wake/warm, gate outcome, time since resume, readiness, page ACK. Измерение выбирается до результата, с известным sampling. Readiness-пробы не должны специально будить фон до контрольного snapshot; ping после обнаруженного failure отмечается как вмешательство. Никаких постоянных heartbeat/ports в основном диагностическом варианте. + +Вспомогательные native поля: wake trigger `navigation / retry / network recovery / context reload / WebKit-or-unknown`, resume-age bucket, current measured navigation kind, diagnostic schema support; пересечения вызовов помечать как multiple/unknown, а не выдумывать один trigger. + +## 6. Как превратить расследование в решение + +1. **Первый небольшой диагностический change:** per-document correlation, worker ingress, bounded pending-await/state queue, native attribution reason и настоящий page ACK. Добавить end-of-episode recovery opportunity. Адаптировать идею #7385 к нынешнему episode contract, а не публиковать старый stacked PR без переноса. +2. **Контролируемые fault cases:** dropped init; config get never settles; state set never settles; native dashboard rejected; response URL/generation mismatch; initResp lost. Для каждого диагностический результат должен различаться. Уже выполненный storage control покрывает первые две storage-развилки на уровне JS. +3. **Физический iPhone, штатные 30 секунд, без debugger/Web Inspector:** foreground idle control; lock/resume 1–5–30 минут; lock during startup; fresh navigation в A и B. Протокол требует и dashboard, и page ACK; после M4 нужна новая regular navigation. Нельзя ограничиваться старой страницей или одним успешным callback. +4. **Сильный причинный A/B:** тот же CPM JS и непостоянный background, но service worker против document background. Сохранить idle unload, одинаковые профили/флаги и реально проверить unload/reload в обеих ветках. Если сбой воспроизводится в SW и исчезает в document при равной нагрузке, это сужает поиск до SW-specific registration/routing/bindings. Изменение сохраняет различия окружения и само по себе не идентифицирует одну строку WebKit. +5. **Второй A/B по нашему коду:** убрать блокировку delivery/health-evidence на сохранении CPM state, сохранив корректность serial writes и настройки consent. Если page ACK здоровый, а старая dashboard-ветка висит, это отдельное исправление наблюдения/state pipeline; оно не должно маскировать реальные потери initResp. +6. Только после совпадающего естественного fault решать о re-init, bounded restart или изменении background environment. Keep-warm оставлять отдельным causal experiment с расходом ресурсов и знаменателем wake, не первым безусловным лечением. + +**Практический следующий шаг:** двигаться с двумя узкими гипотезами — init не дошёл до реально работающего worker; init дошёл, но общий config/state/native/attribution путь удержал наблюдаемый ответ. Новая телеметрия должна первым делом развести их и отдельно показать page ACK. Увеличение одного bg_events хвоста этого не сделает. + +## Артефакты + +`/Users/admin/DuckDuckGo/apple-browsers.git/cpm-ios-idle-wake-investigation-2026-10-30/`: +- `shipped-app/`, `shipped-extension/`, `revisions.json` — проверенные версии и извлечённый исходник. +- `webkit-commit-*.json` — реальные upstream patches; source snapshot отдельно в `/Users/admin/Downloads/WebKit_sparse`. +- `storage-probes.cjs`, `storage-probe-results.json` — три проверки shipped методов, включая healthy control и освобождение заблокированной операции. +- `public-repro/` — прочитанный публичный минимальный пример для сравнения scope. + +Основание количественных выводов и SQL: [[cpm-ios-uncovered-groups-2026-10-30]]. История прошлых воспроизведений: [[cpm-post-release-analysis-2026-10-27]]. diff --git a/work/projects/cpm-ios-uncovered-groups-2026-10-30.md b/work/projects/cpm-ios-uncovered-groups-2026-10-30.md new file mode 100644 index 00000000..984c2977 --- /dev/null +++ b/work/projects/cpm-ios-uncovered-groups-2026-10-30.md @@ -0,0 +1,137 @@ +--- +tags: + - cpm + - ios + - telemetry +updated: '2026-10-30' +status: analysis +--- +# CPM iOS: оставшиеся группы и следующий фокус — 30 октября 2026 + +Продолжение: [[cpm-ios-idle-wake-investigation-2026-10-30]] — browser actions, shipped app/extension/WebKit paths, проверка storage/state queue, публичные аналоги и конкретный telemetry proposal. Уточнение интерпретации M4: `reload_state=loaded` без квалифицирующего нового замера не доказывает провал messaging recovery; отдельно выделены `response_timed_out` и предложен post-recovery opportunity counter. + +Обновлён ClickHouse-срез и проверены актуальные PR. **Следующий основной фокус — потеря ответа после idle wake при уже готовом фоне; второй — старт с `pending` без текущей error 6. Отдельно остаётся непроверенный хвост error-6 history → replacement view → pending: его нельзя объявлять закрытым #7730.** Это выбор следующего расследования и исправления по наблюдаемым симптомам, а не доказанная причинная классификация. + +macOS в этом пересчёте не анализировалась. + +## Данные и проверка + +Источник: Grafana ClickHouse datasource `dekdbd6bqq1hcd`, `metrics.pixels`, agent `ddg_ios`, pixel_id `web`, `web.extension.cpm.episode.{count,daily}.ios.{phone,tablet}`. + +Запрос episodes выполнен **30 октября 11:01:52 UTC / 17:01:52 Бишкек**. Проверка свежести: последние события — **10:50:01 UTC / 16:50:01 Бишкек**. Серверная timezone независимо проверена: `America/New_York`. Все даты ниже — `metrics.date`, то есть Eastern; 30 октября — незавершённый день, а не полный UTC-день. + +Выгружена последняя неделя, 23–30 октября. Основной анализ — **7.243.0, 26–29 октября**, отдельно последний завершённый день 29 октября и частичный 30 октября. 23–25 октября содержат только семь count-событий до публичного релиза и исключены из production-приоритетов. У 7.244.0 найдены лишь два count-события 29 октября; это не выборка эффекта сегодняшних фиксов. Различить public/internal traffic внутри версии по этим полям нельзя. + +Обработаны **404 222 агрегированные строки**. За 26–29 октября: **477 214 count-эпизодов**, **305 853 первых дневных отчёта**, **15 576 подтверждённых cross-tab no-response эпизодов**, **5 902 таких первых дневных отчёта**. + +Независимый SQL повторяет классификацию на сервере; второй контроль проверяет исходы без неё. Завершённые дни сошлись точно, проверены 336 значений групп по окнам и метрикам. На 30 октября последовательные запросы расходятся из-за продолжающейся загрузки событий, даже при ограничении event timestamp. Числа частичного дня привязаны к сохранённому export, а не представлены как неизменяемый snapshot базы. + +Определения: +- `no_response`: `outcome=no_response_before_deadline`, окно около пяти активных минут. Это не доказательство вечной поломки. +- Подтверждение: `repeat_failures=1/2+` или `reload_state≠none`. +- Cross-tab: `tabs_affected=2/3+`, плюс no-response и подтверждение выше. +- `count` — эпизоды. `daily` — первый завершившийся эпизод установки за день; это нижняя оценка тяжёлых случаев, а не «хотя бы один тяжёлый сбой за день». Сумма по дням — install-days, не уникальные пользователи недели. +- Ответ закрывает эпизод, но не доказывает доставку initResp каждой ранее падавшей вкладке. + +Приоритет непересекающихся групп сохранён из предыдущего разбора: error 6 в текущем поле или истории → no view → нет scripts/controller mismatch → update <2 мин → idle wake → context load/baseURL change <2 мин → other. `@30s` означает возраст **меньше 30 секунд**, `@2m` — меньше двух минут. Возраст view дополнительно проверяется по `bg_view_age`. + +## Последний полный день: 29 октября + +Всего **364 695 эпизодов**, из них **9 849 подтверждённых cross-tab no-response**; на daily-leg — **4 090** соответствующих первых дневных отчётов. + +Доли ниже — от этих 4 090 daily-отчётов; число эпизодов указано отдельно: +- **Idle wake: 1 331 / 32,5%; 2 920 эпизодов.** +- **Recent context load: 841 / 20,6%; 1 710 эпизодов.** +- **Error 6, включая историю: 712 / 17,4%; 2 818 эпизодов.** +- Extension updated: 559 / 13,7%; 571 эпизод. +- No background view: 342 / 8,4%; 760 эпизодов. +- Other: 305 / 7,5%; 1 070 эпизодов. +- Tab disconnected/P6: 0 daily, 0 cross-tab эпизодов; 15 подтверждённых no-response эпизодов без cross-tab условия. + +Первые две группы дают **53,1%** таких daily-отчётов. Wake + other — **40,0%**, но это не доказанный объём одного механизма. + +На частичном 30 октября в сохранённом export: 106 436 эпизодов, 3 317 подтверждённых cross-tab no-response; 1 322 соответствующих daily-отчёта. Wake — 372 / 28,1%, recent load — 306 / 23,1%, error 6 — 224 / 16,9%. Это поддерживает порядок приоритетов, но внутридневные частоты с полным днём не сравниваются. + +## 1. Idle wake: крупнейшая группа без отдельного целевого исправления + +Все следующие footprints относятся к **2 920 cross-tab подтверждённым no-response count-эпизодам 29 октября**. + +Форма: `dealloc → view → proxy_on`, часто несколько циклов. У **2 688 / 92,1%** `bg_ready_after=under_1s`; у **2 774 / 95,0%** все восемь native-флагов true. Это делает «просто долго грузился background» слабым объяснением основной части этой группы. Native readiness не проверяет listener и доставку конкретного init/ответа. + +**660 / 22,6%** имеют только повторяющиеся `dealloc>view>proxy_on` в доступной истории, без load, origin change, update, interruption, died или error. Это видимый минимум немаркированного ядра; в **2 038 / 69,8%** всей wake-группы история достигает 12 токенов, поэтому отсутствие старого события ничего не доказывает. + +В истории `died_client` есть у 420 / 14,4%, `died_crash` — у 72 / 2,5%; ни один из них не объясняет большинство. `interruption` в истории встречается у 1 660, но `interrupted=true` самого первого замера — лишь у 50: это разные признаки. У 776 после M4 зафиксирован `reload_state=response_timed_out`; смена контекста сама по себе не гарантировала ответ. + +**Что исследовать для следующего фикса:** путь первого init после idle/resume при background-ready <1 s: дошёл ли init в worker, завершились ли native вызовы, был ли dashboard response и почему он не атрибутировался. Затем — bounded retry/re-init или другая recovery только для воспроизведённого разрыва. Простые длинные idle-тесты ранее были чистыми; повторять только их недостаточно. Нужен сценарий iOS resume/navigation и совпадение footprint. + +1 275 из этих эпизодов на OS <26.6; stable-origin policy там не применяется. Но и на 26.6+ wake не становится автоматически покрытым P3. + +## 2. Старт остаётся отдельным крупным кандидатом; recent load нельзя целиком вычесть как P3 + +Из 1 710 cross-tab эпизодов recent load **1 241 без `base_url_changed` в истории**, и **1 065 из них с текущим background `pending`**. На daily-leg соответствующий pending-поднабор — **585** первых дневных тяжёлых отчётов. Типичная короткая история: `load@30s,view@30s,proxy_on@30s`. У всей recent-load группы только 463 эпизода уже имели readiness <1 s. + +P3 устраняет подтверждённые задержки очистки данных при смене origin; эти симптомы могут частично улучшиться. Однако **recent load не является причинной меткой P3**, а отсутствие `base_url_changed` не исключает origin cleanup при первом контексте процесса: token сравнивает контексты только внутри текущего процесса. + +Следующая проверка после rollout P3 — pending startup отдельно от ready/no-response, с OS <26.6 / 26.6+ и фактическим состоянием флага. P4 late-document recovery в известной конфигурации default OFF; `startup_response_missing` — detection, не применённое лечение. На 26–29 октября найдено 226 933 count detection-события, но без ключа связи с эпизодом это не размер исправляемой тяжёлой группы. + +## 3. Весь error-6 cohort не закрывается PR #7730 + +29 октября из 2 818 cross-tab эпизодов этой группы: +- **508** имеют текущую атрибутированную error 6 в `bg_load_error_after`. +- **2 310** имеют error 6 только в истории. +- **2 276 из этих 2 310** уже относятся к новому текущему view с readiness `pending`; daily-leg этого поднабора — **311**. +- **1 929** имеют не меньше двух error-токенов в хвосте истории. + +Частый pattern: `dealloc → error_6 → view → proxy_on`, повторённый несколько раз. В коде создание нового view очищает текущую атрибуцию load error, но сохраняет lifecycle history. Поэтому broad error-6 group существенно шире «нынешний view упал с error 6». + +[#7730](https://github.com/duckduckgo/apple-browsers/pull/7730), OPEN, head `cf668e2938`: restart того же failed view, если он ещё удерживается; released view сохраняет P2 retry, replacement view намеренно не трогается. PR также исправляет deferred/cooldown scheduling и дедупликацию старых ошибок. + +**Остаток не доказанно закрыт, но и точный uncovered share из этих чисел не выводится:** нет identity failed/replacement view на момент принятия recovery-решения, событий после первого snapshot и связанного outcome treatment. Повторный error token в старом recorder не обязательно означает новую независимую ошибку. + +Нужен отдельный OFF/ON сценарий с released failed view → новый view → pending на точном #7730. Если он остаётся красным, следующий фикс относится к stalled replacement load, а не к ещё одному restart уже покрытого retained view. + +## 4. Other и no-view + +Other: 1 070 cross-tab эпизодов; у 863 readiness <1 s. Часто это те же idle-циклы, но текущему view уже 30 s–2 min, поэтому жёсткая граница wake классификатора переносит их в other. Это возможное продолжение того же пути потери init; считать одинаковую причину установленной нельзя. + +No-view — **158 115 / 43,4% всех эпизодов 29 октября**, но **только 760 / 7,7%** тяжёлых cross-tab эпизодов. Для 151 232 / 95,6% no-view эпизодов зарегистрирован какой-либо последующий ответ. Группа смешивает свежий `load` без view и уже работавший фон после dealloc. Большой объём transient failures не делает её первой целью исправлений browser-wide no-response. + +## Что уже учтено как покрытие + +- [#7753](https://github.com/duckduckgo/apple-browsers/pull/7753) MERGED в `release/ios/7.244.0`; фильтр error documents и итоговые OS/flag ограничения stable origin. Это merge в будущий релиз, не измеренное исправление текущего 7.243.0. P3 [#7493](https://github.com/duckduckgo/apple-browsers/pull/7493) уже merged; runtime effect зависит от версии, OS и включения stable-origin флага. +- [#7767](https://github.com/duckduckgo/apple-browsers/pull/7767) OPEN, head `45593bbb4e`: CPM upgrade-before-load на запуске; private origin переносится только при допустимой policy и отсутствии живого старого контекста. Хорошее целевое покрытие launch-upgrade пути, но не всех событий `extension_updated`, всех runtime upgrades или всех startup hangs. +- #7730 — retained failed view и scheduling, с ограничениями выше. +- [#7784](https://github.com/duckduckgo/apple-browsers/pull/7784) — instrumentation времени navigation readiness; поведение CPM recovery не исправляет. +- [#7385](https://github.com/duckduckgo/apple-browsers/pull/7385) — полезная идея init progress, но PR всё ещё stacked на закрытом #7384 и описывает прежний контракт. Это не готовое покрытие текущих episodes; перенос требует адаптации к действующей схеме и extension bundle. +- P6 на iOS сейчас практически не влияет на cross-tab результат. Graveyard не обоснован этим wake-срезом. + +Production effect ни одного нового PR этим анализом не измерен. Широкие symptom cohorts нельзя вычесть из знаменателя как будто все входящие эпизоды уже исправлены. + +## iOS error pages: уточнение к прежним заметкам + +В [[cpm-post-release-analysis-2026-10-27]] осталось утверждение, что iOS runtime не проверялся. Есть более свежая отдельная проверка 29 октября: реальный diagnostic app на iOS 26.5/27.0, DNS и connection-refused отменяли замер; SSL и phishing предупреждения получали CPM dashboard response; после idle также не было episodes/M4. Подробности: [iOS runtime report](/Users/admin/DuckDuckGo/apple-browsers.git/cpm-ios-error-page-verification-2026-10-29/REPORT.md). + +Это поддерживает отдельный iOS-анализ. Проверка была simulator/dev-build, не полный набор физических устройств и не доказательство отсутствия любых false positives. + +## Рабочая последовательность + +1. Завершить уже подготовленные #7730 и #7767 по их существующему scope. +2. **Новая основная ветка исправлений: iOS idle/resume → init delivery/response attribution при уже ready фоне.** Одновременно отделять совпадающий other. +3. **Второй трек: pending startup без текущей error 6**; измерить остаток после включённого P3 и воспроизвести потерянный первый документ, прежде чем менять late recovery. +4. **Параллельная проверка границы #7730:** historical error 6 → replacement pending. Расширять recovery только после совпадающего воспроизведения. +5. Для разграничения причин нужны init-stage evidence и события после первого failure. Нынешний `bg_events` — только snapshot начала эпизода, один phase, максимум 12 событий / 255 символов; он не показывает развитие сбоя и результат конкретного retry. + +## Воспроизводимость + +Локальный каталог: `/Users/admin/DuckDuckGo/apple-browsers.git/cpm-ios-groups-2026-10-30/`. + +- `episodes.sql/.tsv/.json` — агрегированная выгрузка, без install/session IDs и URL страниц. +- `inventory`, `freshness`, `controls`, `group_verification` — SQL, raw responses и TSV контрольных запросов. +- `denominators_26…30` — запросы отдельно по дням; единый пятидневный запрос превысил лимит 7B rows, после чего разбит на дни. +- `treatments` — события readiness/recovery/detection; связанного episode outcome в них нет. +- `summary.json`, `groups.csv` — разбивки по count/daily, окнам, состоянию, OS и footprints. +- `analyze.py`, `verify.py`, `query.py` — разбор, независимая сверка и read-only runner. +- `pr-*.json` — проверенный GitHub snapshot состава и scope PR. +- `verification-result.json` — точное совпадение завершённых дней и отдельно drift текущего дня. + +Код приложения, remote config и PR не менялись; внешним участникам данные не отправлялись. diff --git a/work/projects/cpm-post-release-analysis-2026-10-27.md b/work/projects/cpm-post-release-analysis-2026-10-27.md new file mode 100644 index 00000000..64a09b3a --- /dev/null +++ b/work/projects/cpm-post-release-analysis-2026-10-27.md @@ -0,0 +1,473 @@ +--- +title: CPM — first post-release data (iOS 7.243.0 / macOS 1.213.0) +date: '2026-10-27' +tags: + - work + - cpm + - analysis +updated: '2026-10-30' +--- +# CPM: first post-release data, 26 October 2026 + +## iOS-only refresh — 30 October 2026, 17:01 Bishkek data cut + +Updated ClickHouse export, independent group checks, background-event footprints and current PR coverage: [[cpm-ios-uncovered-groups-2026-10-30]]. This supersedes the older iOS prioritisation below, not its historical measurements. Latest complete metrics day is 29 October (Eastern); 30 October is partial. The next primary investigation/fix is ready-background idle/resume messaging, followed by pending startup. Error-6 history with a replacement view still pending must not be counted as fully covered by #7730. Today's release-branch merges and open PRs have no measured post-rollout effect in this export. + +A separate 29 October iOS app-runtime report is also included there: checked DNS/connection errors cancelled measurements; tested warning documents returned CPM responses. The older statements below that iOS runtime was never checked are superseded for those tested paths. macOS is excluded from this refresh. + +## Error-6 restart — 30 October 2026 + +[PR #7730](https://github.com/duckduckgo/apple-browsers/pull/7730) is **OPEN, non-draft, MERGEABLE**, head **`cf668e2938`**, with **privacy review required**. The closed draft described under 28 October is historical. This PR is not merged or shipped; this update does not add a new production data cut. + +The implementation restarts only the current embedded context whose **original failed background view is still retained at execution**. Released views keep the existing P2 retry; WebKit's replacement view is left alone. Deferred errors resume after foreground/wake. The latest pending error survives the 60-second cooldown and is retried after expiry; expiry in background waits for foreground. Equal historical errors are deduplicated, with history reset for a newly created background view so delayed clear/add notifications do not hide a new equal failure. + +A dedicated `web_extension_cpm_background_load_restart` pixel now exists, dailyAndCount on both platforms with no macOS name suffix and bounded `outcome`. Applying the restart, background readiness and an attributable dashboard response are separate stages. Terminal outcomes distinguish measured no-response at the five-active-minute deadline, unobserved, superseded, reload failure and reload timeout. A new embedded identifier loaded by upgrade supersedes the old restart observation; its response cannot claim that the old restart recovered messaging. Count rows are required for outcome rates; daily deduplication usually keeps restart_started. + +Local final validation on `cf668e2938`: **1,064 production package tests passed**, strict lint and both platforms' pixel validators passed. Final review added **8 temporary edge-case probes: 1,072 tests passed**, no important findings; the probes were removed. Permanent regressions remain in normal PR CI. Exact-head CI was not re-read for this documentation update. + +The separate review harness is synchronized at `5348c7254e` and adds only 17 Swift fixture/test files plus its guide. The last actual same-binary macOS OFF/ON was run on **`d1a2e1de65`**: OFF functionally failed with real error 6 and a retained failed view; ON restored the original document's native response and real page initResp, with two later idle/wake cycles healthy. This is controlled worker-start fault injection, not a natural production trigger or physical iOS suspend/wake validation. Later queue/cooldown/attribution changes have regression-test evidence, not a new real-WebKit OFF/ON result. + +Full behavior, flags, outcomes and evidence: [[cpm-status-2026-10-15#Error-6 restart — PR 7730]]. Reviewer commands: [[CPM Breakage Testing Steps#Error-6 restart — PR 7730 (30 October 2026)]]. Historical production percentages below retain their original dates/denominators; the PR does not establish its production causal share or post-rollout effect. + +Internal metrics; keep these figures out of public PRs. Previous analysis: [[cpm-metrics-causes-and-mitigations-2026-10-16]], [[cpm-status-2026-10-15]] (§3–5, 12–14 October cut). + +## Upgrade-before-load — 30 October 2026 + +[#7767](https://github.com/duckduckgo/apple-browsers/pull/7767) now implements the extension-updated follow-up. Head **`45593bbb4e`**, latest merged main **`3046f67631`**; **OPEN / MERGEABLE**, not merged or released. Exact-head CI: **43 passed, 15 skipped, no failed/pending checks**, including successful iOS/macOS/BSK/DBP result checks. + +The launch pre-sync handles **CPM only**, without scriptlet downloads, before loading installed contexts. The normal full sync follows; other extension types retain load-before-sync. Failed pre-load upgrades keep the previous installation available and the normal sync can retry after load. Disabled CPM is removed before load. The default-enabled remote failsafe `webExtensions.cpmUpgradeBeforeLoad` is captured when the manager is created; OFF restores the previous order on the next manager/app launch. Origin reuse additionally depends on stable-origin policy, OS 26.6+, a private CPM manifest and no live old context. Runtime upgrades keep distinct origins. + +No new pixel or success tracking was added. Existing install-error pixels mark CPM pre-load errors using **`.before_load` in error domain**, preserving code/userInfo/available underlying errors; ordinary sync retains the original domain. Missing bundle/type mismatch use `EmbeddedSyncError: DDGError`, codes 1/2. The earlier 29 Oct UTC ClickHouse cut found **518 iOS / 16 macOS standard install-error events** across versions; 14 macOS events were invalid archive. No installation/session key connects these pixels to failure episodes, and hourly correlation did not establish causation. `extension_updated` records successful update history. Those queries were not rerun for this update. + +Validation is revision-specific: 1,037 package tests passed after the first main merge; 11 real-WebKit harness scenarios passed on `fafcb1ffda`; 89 manager tests passed after the final telemetry fix. iOS CI initially crashed because the telemetry test read an unavailable package resource bundle. `00ddded2ff` replaces that dependency with a generated ZIP fixture. The corrected test passed on iOS 27 Simulator even with the resource bundle removed; the latest PR head subsequently passed CI. Full iOS app upgrade/startup runtime was not locally validated. + +Harness medians, 20 launches/scenario with three document replies: old order **180.9 / 173.6 / 208.3 / 205.8 ms**; latest CPM-only sample **181.7 / 172.3 / 179.5 / 178.4 ms** for first install / unchanged / upgrade / upgrade with 2,000 cookie-bearing sites. All 240 replies in the latest sample arrived. These are separate package-harness samples, not full-app startup measurements or proof of reduced production hangs. + +**Interpretation of priorities below:** the 26–27 Oct macOS wake/other groups predate the error-page fix and may include false positives. The confirmed local mechanism justifies upgrade-before-load as an actionable follow-up; its production effect is unmeasured. iOS should be analysed separately, not assumed to be a proven clean control: error-page behavior at iOS app runtime was not validated in this work. Compare post-rollout versions/OS/flag states before assigning the old wake cohort to real CPM breakage. + +The release PRs #7752/#7753 are merged, while GitHub still lists 7.243.0 / 1.213.0 as the latest non-prerelease tags at this check. 7.244.0-8+ios and 1.214.0-862+macos are marked prerelease. This does not establish App Store rollout. + +Current implementation, rollback, telemetry, per-head tests and artifact locations: [[cpm-status-2026-10-15#Текущее состояние — 30 октября 2026, 16:48 Бишкек]]. Earlier data cuts and reproduction results below remain historical evidence. + +## Release update — 30 October 2026 + +- **Merged, not yet verified as publicly released:** [iOS #7753](https://github.com/duckduckgo/apple-browsers/pull/7753) into `release/ios/7.244.0` (merge `26a461e30e73608e75e6d84e09bca0ced842993c`) and [macOS #7752](https://github.com/duckduckgo/apple-browsers/pull/7752) into `release/macos/1.214.0` (merge `1e392bac233d7e3e5ccef340ebc82ad7aee72922`). Latest public GitHub releases checked on 30 Oct are still 7.243.0 / 1.213.0. Merge does not establish rollout or production effect. +- **Final stable-origin behavior:** set `baseURL` only on macOS/iOS 26.6+ with the feature flag enabled. Creating a context with an unsupported OS or disabled flag rotates the stored CPM origin and returns `nil` (WebKit chooses the context's origin). Toggling the flag alone does not change a live context; an off→on interval with no intervening context creation does not rotate anything. User reports the remote flag disabled; live remote configuration was not independently audited. +- **Only a storage-key rename:** property `cpmOriginIdentifier` now encodes/decodes JSON key `cpmStableOriginIdentifier`. Old `cpmOriginIdentifier` values are ignored. There is no migration, UUID prefill, OS-version marker or reset on every OS upgrade. Missing new-key value falls back to the already persisted installation `uniqueIdentifier`; this is reuse of that UUID, not creation of a fresh UUID. Subsequent launches reuse it. A later normal rotation saves a replacement under the new key and reuses that replacement. +- **Public-build transition:** 7.243 / 1.213 persist the installation UUID but do not assign it as the extension's baseURL. WebKit chooses a random origin for each new context. The first enabled stable-origin load therefore changes the origin once. An internal build with a replacement saved under the old key falls back to the installation UUID after this key change. +- **Error-page fix:** the health monitor skips error-page commit/finish; macOS maps `.alternateHtmlLoad` to `.errorPage`. These documents no longer start timeout measurements, episodes or M4 reloads. This does not establish what fraction of production wake episodes were error pages. +- **Validation:** the key-only revision passed all 1,025 WebExtensions package tests and strict lint; the final simplified policy suite passed 25 tests, including injected unsupported-OS coverage, with strict lint on both branches. The original release-base error-page change passed three macOS app integration tests (DNS, SSL and malicious-site warning), with a failing negative control when the mapping was removed. Those app tests were not rerun after the key/test-only edits; iOS runtime behavior was not tested. Final branch heads: iOS `fef5f9cdd35977b0cb848ac81f4feaca3e6a9ba6`, macOS `551cb2edd00c02e038b2b46a07a8c32d0b33e666`. +- **Mock follow-up:** the local `StubWebExtensionManager` stays in the release tests by reviewer agreement. Moving them to the shared mock is a [main-branch follow-up](https://app.asana.com/1/137249556945/project/1202406491309510/task/1220003778559454?focus=true). + +### Stable-origin evidence and limits + +The demonstrated benefit is avoiding WebKit's origin-change data cleanup before injection/background startup: real-cache reproductions measured about 1.5 / 10.9 / 32.2 seconds at 20k / 200k / 600k cache entries with changing origins, versus about 0.1 seconds with a stable origin. See [WebKit bug 320812](https://bugs.webkit.org/show_bug.cgi?id=320812) and the reproduction notes below. Production symptom cohorts are not proof that every matching episode has this cause. + +No test demonstrated an extension staying permanently broken after an OS upgrade because its baseURL was saved. A live-session background-process termination did reproduce an error-6 loop that origin rotation recovered. In the real two-process restart baseline, a full app restart restored messaging even while reusing the previous origin. Evidence: `alex/cpm-p3-integration-tests/macOS/IntegrationTests/CPM/README.md`, “two-process” prepare/resume results. Do not turn the live-session failure into a proven persistent-upgrade failure. + +The earlier claim that extension workers were *never* persisted was incorrect. [WebKit commit eff9f9231ad90e31e6ae2b128d2269c589550c8d](https://github.com/WebKit/WebKit/commit/eff9f9231ad90e31e6ae2b128d2269c589550c8d) (312316@main, 29 April 2026) stops new extension service-worker registrations being written to disk; that change does not purge existing records. Historical records are therefore possible, but this does not demonstrate the proposed upgrade failure. + +Pixels can show no response within five minutes, cross-tab failures, error 6 and reload outcomes. They cannot uniquely identify old-origin reuse after an OS upgrade; `base_url_changed` compares contexts within the current process, not across launches. “Permanent” in this analysis means the five-minute reporting outcome, not proof of an unrecoverable extension. + +### OS split of the no-background-view symptom — query on 29 October + +26–28 Oct (metrics date in Eastern time), released iOS 7.243.x / macOS 1.213.x without P3. Count-leg episode rows only, not users: `context_loaded=true AND bg_view_alive=false`, excluding a nonempty `bg_load_error_after` or an `error_[0-9]` event in `bg_events`. + +- iOS: OS <26.6 — 695 of 14,537 failure episodes (4.78%); OS 26.6+ — 27,670 of 97,982 (28.24%). Thus 97.55% of this symptom cohort is on 26.6+. Confirmed no-response subset: 43 / 347 respectively. +- macOS: OS <26.6 — 274 of 13,206 (2.07%); OS 26.6+ — 961 of 29,770 (3.23%). Thus 77.81% of this symptom cohort is on 26.6+. Confirmed no-response subset: 29 / 98 respectively. + +Source: `metrics.pixels`, `pixel_id='web'`, `web.extension.cpm.episode.count%`, agents `ddg_ios` / `ddg_mac_desktop`; OS split by major/minor tuple at (26,6). These are symptom counts, not a causal P3 share or a stable-origin safety comparison. The query was run on 29 Oct; it was not rerun for this documentation update. + +## Priorities — macOS browser-wide breakage first + +macOS fails far more than iOS, so priorities follow **cross-tab permanent failures on macOS**: CPM not answering in two or more tabs for 5 minutes, confirmed by a repeat or reload. These are browser-wide breakage, not one stuck tab. 26–27 Oct, macOS 1.213: **386 users, 1.85% of DAU**. + +Share of macOS cross-tab permanent: idle wake 174 (45%), other 78 (20%), extension updated 77 (20%), recent context load 35 (9%), no background view 11 (3%), P6 9 (2%), error 6 2 (0.5%). + +1. **Idle wake (45%, cause unknown).** + - Check Network-recovery false positives: recovery applies on 7.5% of macOS DAU vs 0.9% on iOS, and `died_client` precedes 16% of macOS idle-wake failures. + - Add telemetry: events after the first failure (two-phase `bg_events`), a healthy-navigation baseline, retry/recovery applied in the episode. + - Keep-warm experiment behind a flag (also a causal test). + - **Update 28–29 Oct:** error pages were found to be counted as CPM failures with exactly the wake signature (reproduced in the harness and in a macOS integration test); fix in PRs #7752 (macOS 1.214.0) and #7753 (iOS 7.244.0). Details and the other checks in the section "Idle wake investigation — 28 October 2026" below. +2. **Extension updated (20% in the historical 26–27 Oct cut).** Implemented in [#7767](https://github.com/duckduckgo/apple-browsers/pull/7767): prepare CPM before load, carrying a private origin only when allowed and no old context is live. The PR is open with green CI; production effect is unmeasured. See the 30 Oct update above. +3. **Other (20%).** No distinguishing signal yet; needs the telemetry from step 1. + +Not priorities: +- **P3 / origin change:** the baseURL fix is in the upcoming 7.244 / 1.214 release branches. Public rollout and an enabled flag are prerequisites for measuring its effect; recent context load is not by itself proof of origin-change cleanup. +- **P6:** 96% of P6 users have a single failing tab; 122 of its 131 permanent users are single-tab, only 9 cross-tab. One stuck tab, not browser-wide breakage. Low priority; find the trigger when convenient (~37% show `extension_updated` before the failure). +- **Error 6 on macOS:** 2 cross-tab users. + +iOS (cross-tab permanent 861 users, 0.26% of DAU): recent context load 38% (a symptom targeted in part by upcoming P3; P3 is absent from this measured release), error 6 20% (error-6 loop: restart the stale background and re-send `init`), extension updated 14% (same fix as macOS step 2), idle wake 14%, other 8%, no background view 5%. + +## Summary for the team — 26–27 Oct + +**CPM post-release analysis: iOS 7.243.0 / macOS 1.213.0, 26–27 Oct (2 full days)** + +Definitions: failure = no CPM dashboard response within 4 s after a page finished. Episode = one failure until resolution or the 5-minute deadline. Permanent = no response for 5 minutes, confirmed by a repeat failure or a reload. Affected users = users who reported a failure that day (daily pixel); all percentages are of DAU. Episodes = every failure report (count pixel). + +**Size** +- iOS: 9.1% of DAU hit a failure, 0.43% permanently. About 3× fewer failures than the previous release. +- macOS: 28.5% of DAU hit a failure, 2.87% permanently. No change vs the previous release. +- Most failures recover by themselves: 89% on iOS, 63% on macOS. + +**Where permanent failures come from** (share of permanent users) +- iOS (0.43% of DAU): recent context load 40%, error 6 20%, idle wake 16%, extension updated 13%, other 7%. +- macOS (2.87% of DAU): idle wake 38%, P6 (tab not connected) 22%, extension updated 15%, other 14%. Browser-wide (cross-tab) only, 1.85% of DAU: idle wake 45%, other 20%, extension updated 20%, recent context load 9%, P6 2%. + +**Persistent mechanisms** (repeat episodes from the same users confirm a stuck state) +- **iOS error-6 loop:** error 6 hits 0.86% of iOS DAU, and 10% of those users stay stuck. Stuck users average 7.4 episodes, recovered ones 1.2. In 38% of error-6 episodes the background keeps failing to load on every wake. Matches the stale-failed-background state reproduced in the harness. *Fix: restart the stale background on error 6 and re-send `init` to the page.* +- **Failures after an origin change (`base_url_changed`):** present in 1,733 iOS users (14% permanent, 3× baseline) and 193 macOS users (16%). On macOS these installs fail repeatedly (13–27 episodes per user in the idle-wake patterns, from small groups). The origin change itself causes a 1.5–32 s blackout depending on cache size (reproduced); P3 removes it. *P3 is merged into upcoming release branches; verify its effect after public rollout and flag enablement.* +- **macOS P6:** 1.6% of DAU, 39% of them permanent, about 2 episodes per user. 96% are a single stuck tab (122 of 131 permanent are single-tab), not browser-wide breakage. ~37% of P6 users show `extension_updated` before the failure, so the mid-session upgrade is a likely trigger. *Fix: find the trigger (upgrade path first), then fix.* + +**Transient, not a stuck state** +- Extension updated: one episode per user, at upgrade time. Still worth fixing (load the new version first, keep the origin), since it repeats after every app update. +- Idle wake: mostly recovers (iOS 88%, macOS 58%). Its repeat-heavy part overlaps the origin-change patterns. Cause unknown; it may just be the state most macOS navigations are in. + +**Caveats:** two days after release, mostly early updaters (macOS ~17k DAU). Loop figures come from small groups. Not measured yet: retry attempts, events after the first failure, a healthy-navigation baseline. + +## What shipped in the measured 7.243.0 / 1.213.0 releases + +- iOS **7.243.0** (tag 26 Oct 19:23 UTC), macOS **1.213.0** (tag 26 Oct 16:23 UTC). +- Both: episode telemetry #7376/#7484, episode tab scope #7520/#7521, P1/P2/P4 recovery #7379/#7536, Network-process recovery #7459/#7531. +- macOS only: P5 shared-pinned window close #7473, remote/debug disable #7498. +- **Absent from these measured releases:** P3 stable origin #7493; it is now in the upcoming 7.244 / 1.214 release branches. **No P6 fix exists in either release**: no PR implements the controller rebind. +- Shipped episode contract is the rolled-back variant: `was_stuck` (bool), single-phase `bg_events` (no `;`), plus `tabs_affected`, `repeat_failures`, `interrupted`. No `bg_view_creations`, `context_errors` or `network_restarted`. Old cohort SQL needs adapting (below). +- Monitor timing: 4 s response grace; suspended time no longer counts (8 s after resume, 30 s extension-load wait, 5 min deadline). + +## How to query + +- New pixels have **no `m_`/`debug_` prefix**: ClickHouse `pixel_id = 'web'`, `pixel LIKE 'web.extension.cpm.%'` (iOS adds `.ios.phone|tablet`). Querying `pixel_id='m'` without an agent filter and a one-day window exceeds the 7B row limit. +- Denominator: `pixel_id='m'`, `m.web.extension.loaded.daily%` / `m.mac.web.extension.loaded.daily`, split by `appVersion`. +- Old-contract attribution was **remotely switched off from 21 Oct** (old versions keep sending bare `debug.web.extension.cpm.*` without parameters). The last fully attributed old-version window is **18–20 Oct, iOS 7.241 / macOS 1.211.x**. +- Data lag: on 27 Oct the table ended at ~01:10 UTC; `date` is US-Eastern. **26 Oct is the only post-release day and it is partial for new adopters** (exposure starts at update time). +- Runner: `~/Developer/grafana-data-tools` reader; ad-hoc SQL and an `ARRAY JOIN extractURLParameters(request)` parameter dump were used. `extractURLParameter` needs a constant key. + +## Rates per extension-loaded install-day (26 Oct) + +| | iOS 7.242 (old) | iOS 7.243 (new) | macOS 1.212 (old) | macOS 1.213 (new) | +|---|---|---|---|---| +| loaded.daily | 4,257,839 | 52,979 | 93,030 | 2,566 | +| old init_failed.daily (sum of reasons) | 21.3% | — | 36.8% | — | +| old stuck.daily | 3.3% | — | 12.5% | — | +| episode.daily | — | **7.05%** (3,735) | — | **30.0%** (771) | +| episode with `reload_state≠none` (hang confirmed, ≈ old stuck), count leg | — | 23% of episodes | — | 26% of episodes | +| `was_stuck=true` (second distinct tab), daily leg | — | 8.1% | — | 11.2% | +| network_process_recovery_applied.daily | — | 432 (0.8%) | — | 232 (**9.0%**) | + +Not like-for-like: the episode unit is one report per episode, the old init number sums four reasons, suspended time is now excluded, and day-1 adopters have short exposure. Directionally, **iOS fell ~3× (21% → 7%); macOS barely moved (37% → 30%).** + +`was_stuck` only becomes true when a *different* tab fails; M4 reload already runs on a same-tab second timeout. Use `reload_state≠none` as the old "stuck" proxy. + +## Cohorts (count leg, adapted classification) + +Priority: error 6 (`bg_load_error_after` present or `error_` token) → no view (`bg_view_alive=false`) → tab not connected (`tab_has_ext_scripts=false`) → extension_updated ≤2 min → wake (`bg_view_age` < 30 s and a `dealloc` token) → recent load/base_url_changed ≤2 min → other. There is no Network-replaced cohort any more. + +| Cohort | iOS share / of stuck / no-response | macOS share / of stuck / no-response | +|---|---|---| +| error 6 | 7.5% / 24.7% / 39% | 0.6% / 1.7% / 44% | +| no view (P3) | 7.7% / 6.8% / 7% | 2.9% / 0.9% / 25% | +| tab not connected (P6) | — | 5.8% / 1.3% / **70%** | +| extension_updated ≤2 m | 3.9% / 6.2% / 37% | 14.6% / 11.7% / 28% | +| **wake, view <30 s** | 22.8% / 22.0% / 9% | **43.9% / 58.4% / 48%** | +| recent context load | 22.8% / 17.2% / 12% | 11.9% / 6.9% / 25% | +| other | 35.3% / 23.2% / 5% | 20.4% / 19.0% / 27% | + +Baseline with last week's exact SQL, 18–20 Oct (init → stuck shares). **iOS 7.241**: error6 6.4→22.0, no view 47.5→11.5, network 16.5→38.2, wake 7.7→9.2, recent load 16.7→6.6, other 5.2→12.5. **macOS 1.211.x**: error6 1.2→2.2, no view 3.6→1.9, tab not connected 5.9→8.7, network 13.2→25.8, wake 33.2→33.3, recent load 19.4→6.3, other 23.6→21.6. This matches the 12–14 Oct cut, so the baseline was stable. + +Per-install rough rates (cohort share × failure rate), old init → new episode: + +- **iOS:** no view 10% → 0.5%, recent load 3.5% → 1.6%, error 6 1.3% → 0.5%; the network cohort is absorbed. **Wake stays at 1.6% → 1.6%.** Other rises 1.1% → 2.5%; it now absorbs former network cases and has no network field. +- **macOS:** wake ≈12% → ≈13%. Unchanged. + +## Findings + +1. **P1 (wake) fix had no measurable effect on either platform.** In the wake cohort `bg_ready_after=under_1s` dominates (macOS 93% overall), so the background is *not* slow. The `background_wake` pixel fires only for ≥1 s callbacks (macOS 89/day vs ~670 wake episodes). The remaining failure is message/response loss after a fast idle wake, not readiness. On macOS 48% of wake episodes see no response for 5 min; tabs=3+ → 68%. Typical tail: `…dealloc@15m,view@15m,proxy_on@15m,dealloc@5m,view@30s,proxy_on@30s`, `bg_view_age=4s_to_8s` (view created around the navigation). The pattern appears on every macOS version, 15.6–27.2. +2. **iOS error 6 changed shape.** All attributed error 6 is now `bg_load_error_after=under_5s`; the 30 s timer bucket is gone. + - **A** (`ready=failed`, app_state active): `dealloc → view → proxy_on → interruption → error_6`, often after `died_client`/`died_crash`/`died_crash_limit`. The background load spans an app suspension and fails at resume. + - **B** (`ready=pending`): an older error 6, then a new view whose load is still pending 8–30 s later. + - The P2 retry fires only while foregrounded with a 60 s cooldown (277/day). **Foreground-resume recovery is the missing piece.** + - *Update 28 Oct: checked with a real HOME and suspension: the retry is skipped in the background and nothing happens on foregrounding, but the page was still answered because WebKit re-woke the background itself; see the 28 October section.* +3. **macOS P6 is untreated**: 5.8% of episodes, 70% never answered. iOS now shows `tab_has_ext_scripts=false` in 5.3% of episodes, concentrated on iOS 26.7 (16%) and 27.0 (8%), with 0 on 27.2. Previously iOS was ≈0. +4. **macOS Network replacement is frequent**: the applied pixel is on 9% of 1.213 install-days. With recovery applied it no longer appears as a cohort, but `other` stays at 20%. +5. **After M4 on macOS**: `reload_state=loaded` → 46% no response by deadline; `response_timed_out` → 57%. The reload often does not restore messaging, or no qualifying navigation follows. +6. Recovery pixels only ever report `background_ready`. `response_received` needs late-document reload, which is default OFF. +7. Day-1 effect: macOS `extension_updated ≤2m` is 14.6% (embedded upgrade on update). Re-check after 2–3 full days. + +## Next measurements + +- Repeat for 27–29 Oct (full days, larger adoption). Track the wake cohort and macOS no-response by OS. +- `bg_events` per-phase budget and `network_replaced_since_view` remain missing (see [[cpm-status-2026-10-15#6. Телеметрия]]). + +## Repro targets + +| Issue | Evidence | Hypothesis to test | Candidate fix | +|---|---|---|---| +| R1 wake message loss | macOS 44% eps / 58% stuck; iOS 23%; ready <1 s | message to a woken non-persistent SW is acknowledged with no reply: (a) `processes(RuntimeOnMessage)` empty when `_loadServiceWorker` completion runs before listener IPC; (b) listener set loses `RuntimeOnMessage` so `wakeUpBackgroundContentIfNecessaryToFireEvents` resolves `{}` without waking; (c) background unloaded while a reply is pending | keep the background warm while CPM pages are active, or re-send init on empty reply (native/JS handshake) | +| R2 iOS error 6 across suspension | 7.5% eps / 25% stuck, all under 5 s, interruption between view and error | WebKit load timer spans suspension; nothing retries on resume | on foreground resume, retry `loadBackgroundContent` when the last load failed or is pending | +| R3 P6 controller mismatch | macOS 5.8%, 70% unanswered | known main-flag manager replacement | rebind/recreate tab web views to the current controller | + +*Update 28 Oct for R2: reproduced with a real HOME and a 45 s suspension. The retry is skipped while the app is in the background, but WebKit created a new view 20–25 ms after the error and the page was answered (0.6 s after resume), so the suspension case self-heals in the simulator. Persistence of this family in production is not explained (172 permanent episodes). See the 28 October section.* + +WebKit source pointers (`~/Downloads/WebKit_sparse`): `WebExtensionContext.cpp` `wakeUpBackgroundContentIfNecessaryToFireEvents` (drops events when the listener set lacks the type after the first load), `scheduleBackgroundContentToUnload` (30 s, reset only on dispatch), `WebExtensionContextCocoa.mm` `loadBackgroundWebView` / `performTasksAfterBackgroundContentLoads`, `WebExtensionContextAPIRuntimeCocoa.mm` `runtimeSendMessage` (empty `processes()` → `{}` reply). 324743 (`9bcf5840ef`) holds runtime messages until SW activation; it is not in shipped OS builds. + +## Repro log — 27 October 2026 + +Worktree `apple-browsers.git/cpm-remaining-failures-repro`, branch `alex/cpm-remaining-failures-repro` (from `origin/main` `8023e4cb5c`, which includes the released P1/P2/P4/Network code plus P3). The package real-WebKit harness was imported from `origin/alex/cpm-repro-harness`. It compiles unchanged on current main, and its CI-tier tests pass. Local tier: `CPM_REPRO=1 swift test --filter ` from `SharedPackages/WebExtensions`. System WebKit is the host macOS; no app windows are opened. + +- **R1 idle wake — not reproduced organically.** `CPMReproIdleWakeTests.testRepeatedIdleWakeCyclesDeliverInit`: 3 tabs, 8 idle-unload cycles (WebKit unloads 32.0 s after the last answered navigation), single and double-tab wakes, 0 of 11 lost. Every wake answered in 2–5 ms after didFinish. `testNavigationAtIdleUnloadBoundaryDeliversInit`: offsets −0.6…+0.2 s around the calibrated unload, 0 of 7 lost. A plain idle wake on an in-memory fixture page is not the production mechanism. The missing ingredient is still unknown: real page loads, a long-lived data store with abandoned registrations, Network replacement, or listener-set loss. +- **P3 (#7493) only in internal builds** (macOS 1.214.0-860, iOS 7.244.0-x). On 26 Oct: macOS 1.214 had 20 episodes / 9 daily of ~36 loaded installs, with 0 no-view and 0 error 6; iOS 7.244 had none. That is too small to evaluate. Relevance: released builds get a new random extension origin on every context load (launch and M4 reload), and WebKit migrates data before injecting. This may explain the iOS no-view cohort and the macOS "reload loaded → still no response" share (46%). Re-check when 7.244 reaches users. +- **R2 suspended wake — macOS analogue does not stay broken.** `CPMReproSuspendedWakeTests`: idle unload, then a navigation wakes the background, and the new background process is SIGSTOPped for 45 s while another tab navigates every 10 s. Two navigations timed out (+9 s, +14 s). Then the background was replaced by a new process (same view slot, new PID) and every later navigation answered in 1–14 ms, including 10 after thaw. No error 6 was recorded on the live context. On macOS the shipped recovery restores messaging within ~14 s of the fault. The harness cannot reproduce iOS suspension, which pauses the UI process's own timers. Likely production sequence: the overdue 30 s unload timer fires at resume, closes the loading view, and records error 6 "under 5 s" (pattern A). Pattern B (new view pending 8–30 s) needs a device or simulator test. +- WebKit hypothesis not hit yet: an SW load failure that **keeps** the background view (`loadBackgroundWebView` completion `!success` without unload). Later wakes would only queue, and every dispatch restarts the 30 s unload timer, so the public `loadBackgroundContent`/P2 retry cannot escape. A candidate mitigation is to restart the background web content process when error 6 is recorded while a background view still exists. Not implemented, because the state has not been reproduced. + +## Treatment pixels, P3 ceiling and permanent hangs — 27 October 2026 + +### Treatment pixels received (26 Oct, count leg, iOS 7.243.0 / macOS 1.213.0) + +| Fix | Pixel (`pixel_id='web'`) | iOS | macOS | +|---|---|---|---| +| P1 wake | `web_extension_cpm_background_wake` | 2,026 | 89 | +| P2 error-6 retry | `web_extension_cpm_background_load_retry` | 277 | 12 | +| Install / upgrade readiness | `…_install_readiness` / `…_upgrade_readiness` | 3 / 24 | 0 / 1 | +| Network recovery | `web_extension_cpm_network_process_recovery_applied` | 476 | 277 | +| P4 detection (flag-independent) | `web_extension_cpm_startup_response_missing` | 450 (daily 327) | 20 (daily 12) | +| Late page reload | `web_extension_cpm_late_document_reload` | 0 | 0 | + +- Every recovery pixel only reports `outcome=background_ready`, meaning the fix was applied and the background came up. `reload_started` / `response_received` exist only for the late page reload, which is default OFF, so it has zero rows on every version including internal builds. **No pixel shows whether the page got its answer after a treatment; the effect has to be read from episode rates.** +- `background_wake` fires only when the wake callback took ≥1 s and a navigation waited, so it is not a count of all wakes. +- **Detection gap for the late reload.** `startup_response_missing` fires with the flag off but requires `startup.isFirstLoad`: the first background load of that context in the session (launch, install, upgrade). `attemptRecovery` (the reload) has no `isFirstLoad` condition and would also run after later wakes. With the flag off, **wake-time cases the reload would treat are not counted anywhere**; 327 / 12 is a lower bound of what it could fix. Both detection and reload only arm when the page commits while the startup is still pending. In the wake cohort the background is ready in under 1 s, so many wake failures are outside both. Counting the would-be-reload wake cases needs a pixel change: drop `isFirstLoad` from detection or add a parameter. Not done. +- Upper bound on the late reload: ~0.6% of iOS / 0.5% of macOS extension-loaded installs; ~9% of iOS / ~2% of macOS installs with a failure episode. + +### P3 (baseURL) ceiling on the new data + +- The earlier "≈half" was the pre-release no-view share: **48% of iOS initial failures**, but only 11% of stuck, mostly self-healing. +- On 7.243.0 without P3, no-view is already **7.7% of iOS episodes / 6.8% of stuck** (≈10% → 0.5% of loaded installs); macOS 2.9% / 0.9%. The drop comes from the new measurement (suspended time excluded, one report per episode) and the shipped readiness gates. +- Direct P3 ceiling ≈ **8% of iOS episodes**, less on macOS. A possible indirect effect: released builds get a new random origin on every M4 reload, and 46% of macOS reload-loaded episodes still got no response. That link is unproven; internal 1.214 / 7.244 data is too thin. + +### Users affected by permanent hangs (26 Oct) + +Denominators were refreshed after more 26 Oct data arrived: extension-loaded installs **iOS 69,088 / macOS 3,045**. "Permanent" = `outcome=no_response_before_deadline` (no qualifying response for 5 min). Single = `tabs_affected=1`, cross = `2`/`3+`. Confirmed = `repeat_failures≠0` or `reload_state≠none`. Daily leg = affected installs. + +| Installs/day | iOS | macOS | +|---|---|---| +| Any failure episode | 4,655 (6.7%) | 876 (28.8%) | +| Single-tab, no response | 354 (0.51%) | 217 (7.1%) | +| ↳ confirmed | 76 (0.11%) | 25 (0.82%) | +| Cross-tab, no response | 129 (0.19%) | 58 (1.9%) | +| ↳ confirmed | 123 (0.18%) | 49 (1.6%) | + +- **Cross-tab is the reliable permanent-hang figure:** ~0.2% of iOS and 1.6–1.9% of macOS users per day; 95% / 84% confirmed. +- Unconfirmed single-tab rows (iOS 278, macOS 192) may be stuck tabs or users who didn't navigate within 5 min. +- **Strict permanent (confirmed single + cross): ~0.3% iOS, ~2.4% macOS users/day.** Upper bound including unconfirmed single-tab: 0.7% / 9%. +- Lower bounds for users: the daily leg describes only each install's *first* completed episode of the day, and episodes lost when the process ends before the deadline are not reported. Count leg (episodes per install-day), confirmed cross-tab: iOS 0.41%, macOS 5.2%; confirmed single-tab: iOS 0.26%, macOS 2.3%. +- One partial day; macOS has ~3k installs on 1.213.0. Repeat on 27–29 Oct full days. + +### Repro statistics note + +18 wake navigations cannot rule out a rare loss. At p=8% a clean run happens 22% of the time. The per-wake production rate is likely ≪1% (many wakes per install-day), which needs ~1,000 wakes for 95% power. `CPMReproIdleWakeTests` now has `CPM_REPRO_FAST_UNLOAD=1`: it calls `+[WKWebView _setApplicationBundleIdentifier:]` with `com.apple.WebKit.TestWebKitAPI`, so WebKit unloads an idle background after 3 s instead of 30 s (~5 s per cycle). It can change other test-runner-only WebKit behavior, so confirm any hit with the 30 s delay. A 1,000-cycle run (~1,500 wake navigations) was started on 27 Oct. +- **Fast idle-wake run, 27 Oct: 0 lost of 1,500 wake navigations in 1,000 cycles** (`CPM_REPRO_FAST_UNLOAD=1`, 3 tabs, single and double-tab wakes, 85 min). With zero failures the 95% upper bound on the per-wake loss rate is ≈0.2% (3/1500). A bare idle-unload → wake race on a fixture page is not the main source of the wake cohort; an extra ingredient is needed (hypotheses H1–H5 below). + +### Hypotheses for the remaining wake hangs (27 Oct) + +The embedded extension (`background-embedded.js`, 2026.10.23) registers `runtime.onMessage` synchronously (`MessageRouter`). CPM `init` = `await remoteConfigJson` → native `isAutoconsentSettingEnabled` → native `isFeatureEnabled` → `refreshDashboardState`. Every native request has a 20 s timeout, with `config_unavailable` / `setting_disabled` fallbacks that still refresh. **A 5-minute silence therefore means init never reached the worker, or the refresh was not attributed.** + +1. **H1 — worker without extension bindings after Network-process recovery (WebKit 324754).** macOS recovery is applied on 9% of installs/day. A worker without `browser.runtime` throws at top level: alive, responsive, "ready", never answers, all tabs, M4 may repeat it. Test: Network crash → recovery restart → probe `browser.runtime` in the worker → N wake cycles. Fix: verify with a ping after restart; restart again or reload if it fails. +2. **H2 — listener metadata loss.** `wakeUpBackgroundContentIfNecessaryToFireEvents` returns an empty reply without waking when the saved listener set lacks `RuntimeOnMessage`. The set is saved after every load, so one load where the worker threw before registering poisons it. Test: a prelude knob that throws or skips `onMessage` on one load only. Fix: treat an empty sendMessage reply as a failure → restart, plus re-send init. +3. **H3 — stale failed background view (iOS error 6, pattern B).** `_loadServiceWorker` failure keeps the view, so wakes only queue; activity keeps restarting the 30 s unload timer; P2 retry uses the same path. Test: fail the worker load without closing the view, with navigations every 10 s. Fix: on error 6 with a live background view, restart its WebContent process, then retry. +4. **H4 — refresh not attributed.** The monitor needs an exact scheme/host/port/path/query match with the committed URL. A cold wake answers later, giving `replaceState` / client-side redirects time to change `sender.tab.url`. `other_tab_response` is 37–53% of outcomes. Test: a fixture with `replaceState` 50–300 ms after load, cold vs warm. Fix: attribute by extension tab ID and treat same-document URL changes as the same document. +5. **H5 — slow cold-wake init.** Three native round-trips plus session storage; explains late answers (`response_after` ≥30 s in 20–35% of answered episodes), not 5-minute hangs. Test: prelude `delay` on `sendNativeMessage` 1–3 after a wake. Fix: answer init from cached config/settings first. + +### H1/H2 tests — results (27 Oct) + +New `CPMReproBrokenWorkerTests` and harness changes (uncommitted): +- `Options.isNetworkRecoveryEnabled`, on by default as in the app; off in `CPMReproWebKitTests.unprotected`. +- Prelude `startFaults` breaks one worker start inside a wall-clock window: `.skipOnMessage` (script runs, no `runtime.onMessage` listener) or `.throw` (top-level throw before the extension script). +- `CPM_REPRO_FAST_UNLOAD` moved to `CPMReproTestCase.enableFastUnloadIfRequested()`. + +| Scenario | Fast unload (3 s) | Production unload (30 s) | +|---|---|---| +| H2 skipOnMessage: one woken worker without `onMessage` | only the broken navigation lost; all later ones answered | not re-run | +| H2 throw: one woken worker throws at top level | broken navigation + 5 busy + 3 idle-wake losses, **tabs 1–2 broken for the rest of the test** | broken navigation + **1** busy loss, then everything answered | +| H1 Network crash, recovery on, 20 / 10 wakes | 2 lost after crash, then **tabs 1–2 never got `initResp`**, tab-3 always fine; all restarted workers ran | 2 lost after crash, then all 10 wakes answered; all restarted workers ran | + +- **H1 not confirmed:** every worker restarted after Network recovery ran its script (bindings present). With the real delay, recovery restores messaging after 2 lost navigations. +- **H2 (listener metadata loss) not confirmed:** one start without a listener only loses that navigation; P1's wake plus re-registration heals it. +- **The fast-mode per-tab breakage is an artifact:** after a confirmed hang and recovery, tabs with an unanswered init kept receiving the dashboard refresh but never `initResp`. That is WebKit's silent `tabs.sendMessage` path (`processes.isEmpty()` → `{}`), and the monitor sent one `was_stuck=true / reload_state=loaded / other_tab_response` episode and nothing more. It **did not reproduce with the 30 s delay**. The TestWebKitAPI identity or the 3 s unload changes WebKit behavior; fast-mode hits must always be confirmed with the production delay. The blind spot it exposed is still real in principle: the monitor cannot see a tab that gets the dashboard refresh but no `initResp`. +- **H3 (stale failed background) reproduced with production timing:** after the throwing start, error 6 is recorded and the failed background view **stays present**. The next navigation 9 s later got nothing, and only the M4 reload (new context) restored messaging. No `background_load_retry` pixel fired, so the P2 retry never completed, as predicted by WebKit's `loadBackgroundWebViewIfNeeded` early return. Cost: at least one extra lost navigation per error 6, plus a full M4 reload. On iOS, error 6 is 7.5% of episodes and 25% of stuck. + +### Production-gap ideas and coexistence screen (27 Oct) + +The harness differs from macOS production in four ways: +1. **Other extensions:** 83% of macOS 1.213 installs run the ad-blocking extension (`m.mac.web.extension.daily.ad.blocking.state.daily` `is_enabled=true` 3,486 of 4,182). It is a module service worker with DNR and four `document_start` all-frames content scripts. iOS has none; Dark Reader runs on ~140 iOS installs. +2. **Real pages:** network loads, iframes, redirects, `replaceState`. +3. **Real app tabs and windows:** visibility, App Nap, multiple windows and pinned tabs; app-hosted harness only, in a VM. +4. **Long-lived data store** with abandoned registrations; weak, since ~30 harness runs already shared one persistent store. + +- **Coexistence screen:** `CPMReproCoexistenceTests` (new `Options.embeddedTypes`), CPM + ad-blocking, fast unload, 1,000 cycles: **0 lost of 1,500 wake navigations** (85 min); at 30 s, 0 of 15. The ad-blocking background started once and unloaded at 30 s, but never woke again on fixture pages, so concurrent wakes of both backgrounds were not exercised. Its content scripts ran on every page. A second extension's content scripts alone do not trigger the wake hang. +- **App Store vs DMG** (macOS 1.213, 26–27 Oct, per loaded install): episodes 20.8% vs 29.7%, wake cohort 9.8% vs 10.6%, cross-tab no-response 1.2% vs 2.4%. Sandboxing does not drive the wake cohort; the DMG excess is likely early-updater bias. +- **Real-page screen** (`CPMReproRealPageTests`): earlier repro tests used `loadSimulatedRequest`, so loads never reached WebKit's Network process. `CPMReproFixtureServer` serves loopback pages through it: cross-origin iframes (`localhost` vs `127.0.0.1`), 302 redirects, `replaceState`, slow images, main-thread blocks. CPM + ad-blocking, fast unload, 1,000 cycles: **0 lost of 1,333 visits** across 7 profiles (~190 each), and no monitor episodes. Real loads, frames, redirects and URL rewrites do not trigger the wake hang. +- **Next hypothesis — origin-changing M4 reload stalls on large data:** most confirmed macOS cross-tab hangs (1.213, 26–27 Oct, `tabs_affected≠1` and no response) have tail `proxy_on>dealloc>view>proxy_on`, view 4–8 s, ready <1 s, scripts present, **`reload_state=loaded` (97+22+22) or `response_timed_out` (34+17+13)**. The reload loaded, yet nothing answered for the rest of the episode. Released builds load the reloaded context under a new random origin, and P3 saw WebKit's origin migration slow with a large cache. Test: `CPMReproReloadOriginTests` fills the persistent HTTP cache (`/fill`, default 20,000 × 2 KB) and runs the M4 reload with released vs stable origin, timing post-reload silence. If confirmed, P3 also fixes hang persistence. +- **Reproduced: origin-changing M4 reload blacks out CPM proportionally to the HTTP cache** (`CPMReproReloadOriginTests`, production timing, CPM only). Fill the persistent cache through real fetches, run the hang reload (`reloadExtension(…, trigger: .cpmMessagingHang)`), navigate every 2 s: + +| Cache entries (2 KB) | Released origin (new random origin per load) | P3 stable origin | +|---|---|---| +| 20,000 | first `initResp` 1.5 s after reload, 0 lost | 0.1 s | +| 200,000 | **10.9 s, 1 lost** | 0.1 s | + +`reloadExtension` itself returns in ~20 ms; the delay is WebKit migrating extension data to the new origin before content scripts run. Roughly linear, ~50 µs per cache entry. Cookie seeding on 5,000 `.test` domains did not create website data records (still 2), so per-site record cost is untested. The same migration runs on every **app launch** in released builds, which links this to the P3 startup cohort. **P3 (#7493) removes it in both cases.** It explains `reload_state=response_timed_out` and extended post-reload silence; whether real caches are large enough for a full 5-minute silence is being checked at 600,000 entries. +- **600,000 entries, released origin:** first `initResp` **32.2 s** after the reload, **5 navigations lost**; still linear (~53 µs/entry). Page loads during the migration were also slower (one took 9 s), so the migration may delay browsing generally, not only CPM. A full 5-minute silence would need ~5–6M entries by extrapolation, so this explains post-reload silences of seconds to about a minute, not the whole deadline. The same blackout hits **every app launch** on released builds. With P3, 0.1 s at every size. + +### What survives the hang reload — code search and tests (27 Oct) + +Telemetry: every confirmed permanent hang (iOS 0.30%, macOS 2.07% of DAU) had the M4 reload run and stay silent, so the broken state survives a full context replacement or the trigger repeats. Checked: +- **Native messaging** (`WebExtensionMessageRouter`, `AutoconsentWebExtensionMessageHandler`): handlers keyed by extension identifier and re-registered on reload; each request is stateless. Not persistent. +- **Monitor reload bookkeeping:** responses from measurements begun on the new generation count; harness reload scenarios closed their episodes. No bug found. +- **WebKit process reuse:** `WebProcessCache` requires `usesWebProcessCache`, default false, and the app sets no process-pool options. Ruled out. +- **`storage.session` stall:** CPM's `refreshRemoteConfig` awaits `getFromSessionStorage("config")` with no timeout, so a stall would hang every `init`. No stall in any screen log; only `tabs.sendMessage` on the busy-page profile. Not reproduced. +- **Network replaced while the background is unloaded** (`testNetworkProcessReplacedWhileBackgroundIsUnloadedKeepsWakesWorking`, 30 s timing): 0 lost in 6 busy navigations and 4 idle wakes. +- **macOS tab URL:** `Tab.url(for:)` returns `content.urlForWebView`, which follows `webView.url` one main-queue hop later; WebKit reads it live for `sender.tab.url`. Harness mode `CPM_REPRO_MACOS_TAB_URL=1` reproduces the lag: 133 real-page visits, 0 lost, **0 monitor episodes**. `init` arrives after the hop. +- **`webExtensionPageWebView`:** only set for `webkit-extension:` pages and cleared on content change. Not relevant. + +**Reinterpretation:** "idle wake" is a *state at failure*, not proven to be a cause. On desktop the background unloads after every 30 s idle, so a large share of all navigations, healthy ones included, run right after a wake, and failures from any cause land in this group at that base rate. That fits P1 changing nothing. Confirming it needs a healthy-navigation baseline (background state on successful measurements); none exists since #7384 was closed. + +Remaining untested production ingredients: the real macOS `Tab` stack (DDG user scripts / content-blocking rule lists in tabs, multiple windows, pinned and hidden tabs, App Nap); needs the app-hosted harness in a VM. + +## Plan and corrections (27 Oct) + +### Corrections +- **P6 is not the feature flag.** `webExtensions` is on in production, not toggled, and will be removed. The flag-cycle mechanism (also in the `tabControllerMatchesContext` doc comment) was an old unverified hypothesis. Established: a tab's web view gets the controller only at creation and only if `webExtensionManager` exists then (`WKWebViewConfigurationExtensions` → `attachWebExtensions`), and the manager is created once at launch and replaced only by the flag handler. **Trigger unknown.** Candidates: web views created before the launch-time manager; the flag evaluating off at launch for some installs (missing or stale cached config); a web view built outside the standard configuration path. Macos 1.213 on 26 Oct: 51 users with controller mismatch + no scripts, 19 permanent (22% of macOS permanent hangs). The fix depends on the trigger. +- **Error 6: the P2 retry is shipped and helped.** iOS error-6 users fell from 1.3% to 0.5% of DAU, and 455 iOS users/day had a successful `background_load_retry`. Error-6 episodes have outcomes: iOS 86% recovered later, 9% permanent. It is still 25% of iOS stuck because (a) the retry restores the background but not the page whose `init` was lost, which nothing re-sends (late reload off); (b) the retry is skipped while the app is in the background, within the 60 s cooldown, or during M4; (c) when the failed background view is kept (H3, reproduced), the retry cannot complete. Which dominates is unknown: no retry attempted/result field in the episode. + - *Update 28 Oct: (a) was not reproduced for suspension (the page is answered after resume, WebKit creates a new view); the retained-view case (b/H3) is reproduced separately. See the 28 October section.* +- **Extension updated: yes, the outdated version is loaded.** `WebExtensionLifecycleCoordinator.loadAndSync` runs `loadInstalledExtensions()` (the previous, now outdated version) before `syncEmbeddedExtensions`, which installs the bundled version under a new identifier, loads it alongside, and unloads the old one. Early pages after an app update run against the old version, then the context is swapped with a new origin and the migration blackout. + +### Next steps + +Order superseded by **Priorities — macOS browser-wide breakage first** at the top of this note (28 Oct, two-day data); the items below remain the detailed list. + +1. **Ship what's understood:** + - P3 release. + - Extension updated: upgrade before load at launch; carry the origin across upgrades. + - Error 6: restart the stale background on error 6 when the failed view is still alive, and re-init the page that lost `init`. + - *Update 28 Oct: implemented as the restart in the section "Proposed fix" below (PR #7730, closed, branch kept). The "re-init the page that lost `init`" part was not needed in the simulator: the page was answered after the restart.* + - P6: find the trigger first (the three candidates above), then fix. +2. **Verify the macOS Network-recovery false-positive lead:** recovery applies on 7.5% of macOS DAU vs 0.9% on iOS, and `died_client` precedes 16% of macOS idle-wake failures. Review `CPMNetworkProcessRecovery` detection; count recovery pixels in the harness screens. +3. **Telemetry** (one privacy review): events after the first failure (two-phase `bg_events`); a healthy-navigation baseline (background state on successful measurements); retry attempted/result; recovery applied in this episode; time since app resume. +4. **Targeted harness work only:** Network-recovery restart racing a wake; iOS resume-time wake on the simulator (fix the `Bundle.module` skip); the app-hosted macOS harness in a VM if steps 2–3 leave the wake group unexplained. +5. **Mitigation experiment:** keep the background warm while browsing (periodic `loadBackgroundContent` < 30 s), behind a flag; it is also a causal test for the wake group. +6. Re-measure on full days of 7.243 / 1.213 and the first P3 builds. + +## Data export — 26–27 Oct (built 28 Oct) + +One CSV: `cpm-failure-groups-2026-10-26_27.csv` (iOS 7.243.0 / macOS 1.213.0, both full days combined). Per platform: an ALL row, then each symptom group's total and its top 5 event patterns plus the remainder. Columns: users (failing install-days), % of platform failures, % of DAU, recovered, no response unconfirmed, permanent, % permanent, % of all permanent on the platform. Repeated idle cycles are collapsed as `(dealloc>view>proxy_on)xN`; `[capped]` marks timelines that hit the 12-event limit. Totals cross-checked: iOS 29,758 failing install-days, macOS 5,946. + +DAU proxies: macOS `m.mac.daily.active.user.d`; iOS `m.app.return.ios.{phone,tablet}.daily`. Users = `.daily` leg (install-days when combined); episodes = `.count` leg. Group priority: error 6 → no background view → tab not connected (no scripts or controller mismatch) → extension updated ≤2 min → idle wake (view <30 s after a dealloc) → recent context load ≤2 min → other. End state: recovered = any response; unconfirmed = no response, no repeat, no reload; permanent = no response with a repeat or reload. + +Two-day headline (% of DAU-days): iOS failing 9.1%, permanent 0.43%; macOS failing 28.5%, permanent 2.87%. Largest permanent source: iOS recent context load (40% of permanent), macOS idle wake (38%), then macOS P6 (22%, 39% of P6 users permanent). + +## Repro attempts and footprint comparison — 28 October 2026 + +Internal metrics; keep these figures out of public PRs. Data: ClickHouse via `~/Developer/grafana-data-tools` reader, iOS 7.243.0, 26–27 Oct, `web.extension.cpm.episode*.count`, episodes with `bg_load_error_after` present. + +### What production shows + +- 3,114 error 6 episodes, 229 permanent (`outcome=no_response_before_deadline`, 7.4%). +- Permanent, by error timing and whether the background view is alive at report time: + +| Group | Episodes | Share of permanent | +|---|---|---| +| error `under_5s`, view alive | 172 | 75% | +| error `25s_to_35s`, view not alive (repeated `view,proxy_on,dealloc,error_6` cycles, about one per minute) | 51 | 22% | +| error `under_5s`, view not alive | 5 | 2% | +| error `over_35s`, view not alive | 1 | 0.4% | + +- The 172 look like the recovered ones. Same group, permanent vs recovered (2,797): `interruption` in the footprint 98% vs 96%, `died_client` 22% vs 22%, `extension_updated` 10% vs 6%, none of the three 3 vs 92. Permanent rate inside the group about 5.8%. These tokens do not explain why they stay broken. +- No `unresponsive` token in any permanent group. +- `bg_view_alive` cannot tell "WebKit kept the failed view" from "WebKit created a new view after the error". No field separates the two. + +### What I reproduced (iOS Simulator, iPhone 17, iOS 26.5, real CPM, real page) + +1. **Suspension during a background load** (SIGSTOP of the app and WebKit helpers for 45 s, with a real HOME; also with the start slowed to 8 s): the overdue 30 s unload timer fires at resume, error 6, view released, WebKit creates a new view, the page is answered. Footprint `dealloc,view,proxy_on,interruption,error_6` matches the dominant production footprint, but it self-heals. Persistence of this family in production is NOT reproduced. +2. **Exception thrown at service worker start** (injected, keyed on the extension origin): error 6 within 0.4 s, no underlying error, the failed view stays alive (WebKit `_loadServiceWorker` with success NO records the error and returns without unloading), the retry never completes, the page is unanswered, the second wake fails the same way, M4 heals after about 54 s. The footprint is not the dominant production one. PR #7730 (restart the context when the failed view is retained) fixes it in the simulator (restart 8 ms after the error, page answered after 0.6 s). The PR was closed before this was proven; branch `alex/cpm-error6-restart` kept. How often this state happens in production is unknown. +3. **Hung start** (busy wait of 90 s in the worker): the 30 s timer unloads the view, error 6 about 35 s after creation, view released, the retry hangs again, M4 heals after about 54 s. Footprint matches the 25–35 s loop group. M4 healed only because it re-extracts the clean extension; real persistence across M4 is NOT reproduced. The pixel the app logged for this run does not equal production (`bg_events=load@2m`, `bg_ready_after=pending`, `bg_view_age=8s_to_30s`), because the snapshot is taken at the first failed navigation, before the first loop ends. +4. **Extension update during a background load** (2026.10.23 over 2026.10.99): error 6 on the superseded context only, the new context answers. +5. Separate finding: after any app relaunch the embedded worker stays silent in the simulator (stale registration: `importRegistrations failed to retrieve main script`, then `Found directly reusable registration`), no error 6. Whether a device does this is unknown. + +### Reason for a failed start can be read + +The background page loads with `navigator.serviceWorker.register(...)`; its rejection carries the WebKit error text (`Error: `), obtainable with a `WKUserScript` and `WKScriptMessageHandler` on the background web view delivered by `didCreateBackgroundWebView` (before the page loads). Message only, no file or line. The extension's own `extensionException` messages are not handled by the app (unknown method). Verified once with an injected exception. + +### Historical draft — 28 October 2026: retained-view restart + +> Superseded by [[cpm-post-release-analysis-2026-10-27#Error-6 restart — 30 October 2026]]. PR #7730 is now OPEN at `cf668e2938`; the following closed/no-new-pixel/three-test statements describe the 28 October draft, not the current implementation. + +- **Behavior.** On error 6 (current context, recovery flag on, app in the foreground, no M4 in progress, 60 s cooldown) the manager checks `WKWebExtensionContext._backgroundWebView`. If the failed view is still alive it reloads the embedded extension at once (new reload trigger `cpmBackgroundLoadFailure`) and loads the background. If the view was released it keeps the plain P2 retry, which preserves the failed document's init. +- **Why.** WebKit's `loadBackgroundWebViewIfNeeded` returns early while the view exists (`m_backgroundWebView` set), and after a failed `_loadServiceWorker` WebKit records error 6 without an underlying error and does not unload the view. The retry therefore cannot replace it. +- **Flag.** `cpmBackgroundLoadRestart`: remote kill switch, default enabled in iOS and macOS `FeatureFlag`, off by default in the manager API. Separate from `cpmBackgroundLoadRecovery`, so turning it off keeps the plain retry. Feature Flags Registry task: https://app.asana.com/1/137249556945/project/1211834678943996/task/1219970564251181 (created by a parallel session, linked in code). +- **Telemetry.** No new pixel. The only change is a new value `cpm_background_load_failed` of the `trigger` parameter of the existing reload pixels, and those fire only when the reload itself fails. Nobody could count how often the restart ran or whether it helped. Proposal, not done: a recovery pixel like P2, for example `web_extension_cpm_background_load_restart` with `outcome=background_ready`; needs the pixel definition and privacy review. +- **Tests.** Three unit tests in `WebExtensionManagerTests` (restart with a retained view, plain retry with the flag off, plain retry with a released view). They run on mocks and show only the manager's decision; red without the branch, green with it, also in release. They do not reproduce the failure. +- **Real check.** Simulator, real CPM, injected exception at worker start: without the fix the page is unanswered until M4 (about 54 s); with the fix `Reloading extension` comes 8 ms after the error and the page is answered after 0.6 s. +- **Branch.** `alex/cpm-error6-restart`: `49d4bbb309` (test), `4997652d87` (fix, flag), `a417730e60` (registry link). PR #7730 was a draft, closed on 28 Oct because it was opened before the problem was reproduced; the branch is kept. +- **Does not cover.** The 25–35 s loop (view is released each time), the `interruption` family (suspension self-heals in the simulator), and any case where the retained view is not the problem. How often the state occurs in production is unknown (no field). + +### Open + +- Why 172 permanent episodes of the `interruption` family do not recover, when 2,797 identical-looking ones do. +- What makes a start hang longer than 30 s (candidates from earlier notes: origin-change data migration, host main thread stall, process throttling), and why that survives M4. +- How often the failed view is retained. +- Historical 28 Oct gap, now implemented in PR #7730: `web_extension_cpm_background_load_restart` with bounded outcomes. Privacy review and release/rollout are separate gates; see the 30 October update above. + +## Idle wake investigation — 28 October 2026 + +Re-cut of the post-release data (macOS 1.213.x, 26–27 Oct, matured since the sections above, so shares differ a little; per-episode `web.extension.cpm.episode.count`) and of the old contract (1.211.x / 1.210, 18–20 Oct, `debug.web.extension.cpm.messaging.stuck..count`, one report per tab, confirmed by a second timeout). Not like-for-like: compare shapes, not percentages. OS comes from the `os_version` column of `metrics.pixels`, not a pixel parameter. Wake = the token rule `view@30s` plus an earlier `dealloc` (excluding no-view, no-scripts and error 6). + +### Was it there before Network recovery and P1? Yes +- 1.211.1 shipped neither fix and already had wake at ≈33% of stuck; 1.213 is the same. +- `died_client` in `bg_events` means the app killed the background process. The only shipped code path that does it to a background view is `CPMNetworkProcessRecovery` (`_killWebContentProcessAndResetState`). It is **0 of 65,374** reports in 1.211 and 3.6% of 1.213 episodes (5.1% of wake ones). It is *more* common among wake episodes that got an answer (9.2%) than among those that did not (2.3%). So recovery is a small new source of delays, not the cause. The "16%" in the priorities section did not reproduce with this cut (probably another denominator). +- 1.211 `network_restarted` is depleted in wake (12% vs 23% outside wake; 1.210: 14% vs 21%), without any recovery shipped. + +### What the wake group looks like +- Everything the pixel can see is healthy (7,787 episodes): `context_loaded`, `bg_view_alive`, `bg_process_responsive`, `handler_registered` true in 100%; `bg_process_alive` 99.95%; `bg_ready_after=under_1s` 98.8%; scripts present and controller matching 93% (3.8% P6, 2.9% unknown); error 6 0.3%; interrupted 0.03%. Outside wake the same fields do find things (no view 5.8%, no scripts 7.9%). +- `bg_view_age` 4s–8s in 66% (view created at the navigation), `tabs_affected=1` in 78%, no response 57.6%, `reload_state` none 70% / loaded 19% / response_timed_out 11%, `reason=other` 90.5%. +- The timeline tail of permanent cross-tab wake (1,134) is the same as for answered ones (`dealloc>view>proxy_on` twice, 71% vs 67%). It does not separate them. +- **No OS signal.** Wake share 43–57% on every macOS 15.x / 26.x / 27.x; 27.2 is inside the range (wake 47.7%, no response 42.7%, unmarked 53.7%). Episodes per loaded install-day ≈0.7–0.9, 27.2 at 1.07. +- **Unmarked core.** Wake no-response episodes with no `base_url_changed`, `extension_updated`, `load`, `died_*`, `error_*`, `interruption` or `unresponsive` in the 12-event window: **54% in 1.213 (2,319 of 4,279)**, **52% in 1.211** (15,089 of 28,849; also no network restart, app update or memory pressure). It is uniform by hour, OS, dmg/App Store and tab count. In 1.211 it is 94% `app_launch_age` >15 min and 86% `bg_view_creations` >5: long sessions with many 30 s cycles, not startup. Nothing in the available parameters separates it. +- `extension_updated` is depleted in wake in both versions (2.8% vs 9.4%, 15.1% vs 30.7%); recent updates are not a trigger. `base_url_changed` was enriched in 1.213 permanent cross-tab wake (25% vs 13% of answered; tail 14.8% vs 5.8%) but is depleted in 1.211 wake (13% vs 28%): the lead does not replicate. Both timeline tokens fall out of the 255-character window after a few idle cycles, so older updates are invisible. +- **Context error 7 is not a lead.** The old `context_errors` showed `WKWebExtensionContextErrorDomain:7` (= `ScriptExecutionError`, no underlying pair; WebKit also raises it for `console.error`) in 27% of 1.211 wake reports vs 17% outside, and 22% vs 16% on 1.210. The rate rises with context age (0 / 6 / 20 / 22 / 26% for `bg_view_creations` 0 / 1 / 2 / 3–5 / >5) and wake differs by ≤5 points inside a bucket. The shipped contract keeps other context errors locally and sends only error 6. +- **Extension load-error pixels are negligible:** `load.error`, `embedded.not.loaded`, `reload.failed` are ≈0.1% of loaded installs or less in both versions (1.213: 9 / 8 / 5 events on 18,299 install-days; 1.211 one day: 17 / 101 / 72 on 102,348). +- **Session restoration** is 4.5% of macOS episodes (16% no response) and 21% of iOS episodes (9.6% no response); only 0.6% of the wake group. It mostly recovers; `reason` labels only a first failing navigation that is itself restored. + +### History of reproductions vs this signature +- 8 Oct: a 5 s busy-wait in the worker at wake reproduced the *coarse* wake signature of that time (`responsive=true`, no errors, `dealloc/view/proxy_on@30s`) but recovered about 1 s later; 12 s plus a next navigation gave `stuck` and M4. Production `bg_ready_after` is under 1 s, so a slow worker start does not explain the permanent group. +- Reproduced but different: `kill -9` of the background (all tabs, self-heals ~1 min), Network crash (fixed by recovery), stale failed view H3 (iOS pattern B), origin-changing M4 blackout (seconds to ~32 s), P6 (single tab). +- Not reproduced: plain idle wake (0 lost of 1,500 and 1,333 wake navigations, with ad-blocking and real pages). Nobody has reproduced the permanent, cross-tab, healthy-looking, ready <1 s case that survives M4. + +### What the monitor measures: http(s) only, but error pages slip through +- `isEligibleForCPMMessagingHealthMeasurement` accepts only `http`/`https` (`CPMMessagingHealthMonitor.swift:1094`). `about:blank`, `duck://settings`, `duck://bookmarks` and `duck://player` are not measured (harness test `testNonHTTPPagesAreNotMeasured`: no episodes). Events come only from tabs (`PrivacyDashboardTabExtension`, `TabViewController`); PIR web views were not checked. +- The embedded extension manifest (`duckduckgo-privacy-extension/browsers/embedded/manifest.json`, 2026.10.14) matches `*://*/*` in all frames with no `exclude_matches`; the app grants every requested pattern (`WebExtensionLoader.swift:271`). There are no per-site exclusions. The content script is plain autoconsent; a main-frame `init` always ends in a dashboard refresh. +- **Error pages are measured as CPM documents and reported as failures (reproduced in the harness).** macOS shows a failed load with `loadAlternateHTML(html, baseURL: duck://error, forUnreachableURL: failedURL)` (`Tab.swift:1620`). Then `webView.url` is the failed http(s) URL, so the filter passes; the navigation action URL for alternate loads is also `webView.url` (`NavigationAction.swift:183`); `PrivacyDashboardTabExtension` does not skip `.alternateHtmlLoad` (`PageSignalsTabExtension` does). In `CPMReproErrorPageTests.testErrorPageIsNotReportedAsCPMFailure` (worktree `cpm-remaining-failures-repro`, uncommitted; `CPM_REPRO=1 swift test --filter CPMReproErrorPageTests`): navigation failed, then the alternate load finished with the failed http URL; **no `init` reached the background and no content-script mark appeared** (the script does not run on the error document); 4 s later the monitor counted a failure, and the next healthy navigation closed an `other / failed_tab_response / tabs=1` episode. The harness feeds the monitor `webView.url` as iOS does; the macOS `Navigation.url` path was read in code, not run in the app. iOS error pages were not tested. +- **Hypothesis, not verified:** pages that fail to load are shown on every tab after wake from sleep or a network drop. Each would count as a CPM failure right after an idle unload (fresh `dealloc>view>proxy_on`, background ready in under 1 s, healthy diagnostics), in several tabs (cross-tab) and with no answer until a normal navigation (permanent-looking, and M4 would not help). A same-tab second timeout also triggers M4 on a healthy extension (see `was_stuck` note above), which then adds the origin-change blackout. The pixel has no URL, so data cannot separate this; the check has to be an app run (debug build, Wi-Fi off, several tabs, wake) with the monitor log, or filtering `.alternateHtmlLoad` / `duck://error` and comparing wake rates. + +### Notes +- **Release status corrected 30 Oct:** P3 is absent from the measured public 1.213.0 / 7.243.0 tags. #7752 / #7753 are merged into the upcoming 1.214.0 / 7.244.0 release branches; public release and remote enablement remain separate checks (see the release update above). +- Scratch SQL runner and queries are in the session scratchpad; the harness test is not committed or pushed. + +### Addendum 28 Oct — error pages produce the wake signature (replaces the first version of this addendum) +- **Pixel:** `m.mac.errorpageshown.other` (+ `.daily`, `pixel_id='m'`, params `d`/`e` = error domain/code, `pixelSource`; also `…webkittermination`), fired in `Tab.swift` when the error page is shown. macOS 1.213, 26–27 Oct: 7,511 shows, 2,284 install-days (≈3.3 per affected install-day). Codes: DNS `-1003` 29%, TLS `-1200/-1202…` 26%, `-1005` 10%, offline `-1009` only 1.6%. **Any** error page counts, not only "no network"; the sleep / offline story from the first version is not needed. +- **The wake happens at the navigation, the error comes later.** The navigation gate asks `initialLoadWaiter`, which calls `manager.prepareEmbeddedBackgroundForNavigation()` → `context.loadBackgroundContent` at policy time of every main-frame http(s) navigation (`WebExtensionLifecycleCoordinator.swift` ~80–105, `WebExtensionManager.swift` ~993; macOS caller `PrivacyDashboardTabExtension.decidePolicy`). So a navigation to a dead host after an idle unload recreates the background first and fails afterwards. +- **Reproduced in the harness** (`CPMReproErrorPageTests.testErrorPageAfterIdleUnloadDoesNotWakeOrFail`, production 30 s unload, real navigation to an unresolvable host, then `loadAlternateHTML(… forUnreachableURL:)` as the app does; worktree `cpm-remaining-failures-repro`, uncommitted): the background view was created ~2 ms after the policy decision, the request failed 0.15 s later with the view present, the error document sent no `init`, the monitor counted the 4 s timeout and closed `other / failed_tab_response / tabs=1` on the next healthy navigation. That is the wake signature: `dealloc>view>proxy_on` with a view younger than 30 s, `bg_ready_after` under 1 s, healthy diagnostics, `reason=other`, one tab. The snapshot is taken ≥4 s after the error page finishes, so `bg_view_age` lands in 4s_to_8s (66% of wake) or 8s_to_30s (33%), as in the data. (A first test without the real navigation did not wake the background.) The harness feeds the monitor `webView.url`; in the app, `PageSignalsTabExtension` skips `.alternateHtmlLoad` in `navigationDidFinish`, which shows that handler also runs for error pages, and the alternate-load action URL is `webView.url` (`NavigationAction.swift:183`). +- **Magnitude, install-days (separate pixels, not joined by install):** error-page `.daily` on CPM-capable macOS (15.4+): 1.213 **10.3%** of `loaded.daily` (1,891 / 18,299); 1.211.1 **10.2%** (10,507 / 102,806, 19 Oct). Wake-signature install-days: 1.213 **13.3%** (2,443 `episode.daily` rows), 1.211.1 **14.6%** (15,058 first-failure `.daily` rows; 5.5% for confirmed `stuck`). Error pages per affected install-day ≈3.3, wake episodes per wake install-day ≈3.2. This is a ceiling, not a proof: only an error page after an idle unload carries the wake tag (a warm background gives `other`), and some error pages may not take the alternate-load path. +- **Why it looks permanent, cross-tab and immune to M4:** a second timeout in a tab runs M4, which reloads a healthy extension (new origin, 1.5–32 s blackout). Error pages in two or more tabs with no healthy navigation within 5 minutes end as `no_response_before_deadline`, `was_stuck=true`, `reload_state=loaded/response_timed_out`, with healthy diagnostics. That matches the cross-tab permanent wake group without any CPM breakage. Not checked: how many permanent cross-tab episodes are such loops. +- **Weak evidence against, kept for the record:** per-OS correlation of error pages per install with wake episodes per install is r = 0.02 (n = 9); hourly r = 0.88 for both wake and non-wake (rollout trend, uninformative). +- **Unverified:** the running app (the scenario with `log stream --level debug --predicate 'subsystem == "WebExtensions" AND eventMessage CONTAINS "CPM Health Monitor"'`: three tabs idle >30 s, load a dead host in each, expect `timeoutElapsed` without `Dashboard response attributed`); iOS error pages; the share of wake episodes that are error pages (the episode pixel has no URL or error code). +- **Decisive options:** (a) macOS build where the monitor ignores error documents (`.alternateHtmlLoad` / failed provisional navigations) and M4 never fires for them; compare wake share and permanent rate with the current build; (b) a flag parameter on the episode pixel (needs the privacy review you wanted to avoid). +- **Fix, tests and PRs (updated 30 Oct):** + - **PRs merged:** #7752 into macOS 1.214.0 and #7753 into iOS 7.244.0; merge commits and final branch heads are recorded in the release update above. The sibling branches contain the same intended WebExtensions/error-page changes on their respective release bases. + - **Commit content:** error-page monitor filtering and macOS alternate-HTML mapping; OS 26.6+ and flag gate; stored-origin rotation on a disabled/unsupported context load; JSON key rename to `cpmStableOriginIdentifier` with installation-UUID fallback. No migration or OS-version tracking. Harness/reproduction suites are not included. + - **Run results:** key-only revision: all 1,025 package tests passed. Final simplified policy suite: 25 passed, including unsupported-OS coverage; strict lint passed on both branches. Three macOS error-page app integration tests passed before the key/test-only edits, with a negative control; no iOS runtime run. See release update for the exact scope. + - **Integration tests** (real `Tab`, `macOS Integration Tests`, run locally with the signed Debug build): ordinary load error (`-1003`), SSL (`NSURLErrorServerCertificateUntrusted`), malicious-site warning (`MaliciousSiteError` via the `duck://error` redirect) all pass. Negative control with the macOS mapping removed: the test fails, the monitor receives `["started:other", "failed", "started:other", "committed:https://testhost.com/", "finished:https://testhost.com/"]`, the error page as an ordinary navigation of the failed URL (the false positive in the app's own `Tab`/`Navigation` path); mapping restored. They use their own `StubWebExtensionManager`; `WebExtensionsTestSupport/MockWebExtensionManaging` exists but that product is not linked to the Integration Tests targets. + - **Running locally:** do not build with `CODE_SIGNING_ALLOWED=NO`: the host app then lacks the keychain entitlement (`-34018`) and macOS asks for keychain access. Use the normal signed Debug build, `xcodebuild … test-without-building -only-testing:"Integration Tests/CPMErrorPageMonitoringTests"`. +- **Corner cases checked (29 Oct), no further false-positive source found:** + - Harness (`CPMReproCornerCaseTests`, real WebKit, real extension, loopback HTTP), all pass: HTTP Basic auth accepted (CPM answers) and cancelled (401 page, no episode); client redirects at 0.1 s, 2 s, meta refresh and a redirect after the 4 s grace (CPM answers on the target, no episode); PDF, text/plain and JSON documents (content scripts run on PDF too); a Fire-window tab with a non-persistent store; a download link (WebKit fails the provisional navigation, no document, no episode); 25 tabs loaded at once (all answered in under a second). A 6 s JS timer redirect never fired in a window-less web view (timer throttling), a test artefact. + - Tab close during a measurement is covered by existing monitor unit tests. Policy redirects are exercised by the malicious-site integration test. + - iOS by code reading only: SSL/malicious special pages load via `loadSimulatedRequest` for the failed URL, an ordinary document on which CPM answers; other load errors use a native error view without a document. + - **Not checked:** real minimised/occluded windows and App Nap (harness web views have no window and still answer in milliseconds); http→https upgrade through the app's HTTPS upgrade store; popups from `about:blank`; iOS app behaviour at runtime. +- **What this does not settle:** the share of wake episodes that are error pages is unknown (the episode pixel has no URL or error code); the install-day ceiling is ~10% (error pages) against 13–15% (wake). Real CPM breakages stay as before: error 6 on iOS, origin-change blackout, P6, extension update, `died_client` (~5% of wake). The check after release: wake episodes per loaded install-day should drop by roughly the error-page share, with `m.mac.errorpageshown.other` as the control. diff --git a/work/projects/cpm-status-2026-10-15.md b/work/projects/cpm-status-2026-10-15.md index f638c49a..71494c5e 100755 --- a/work/projects/cpm-status-2026-10-15.md +++ b/work/projects/cpm-status-2026-10-15.md @@ -5,10 +5,78 @@ tags: - work - cpm - status -updated: '2026-10-20' +updated: '2026-10-30' --- # CPM Web Extension: единый статус +## Error-6 restart — PR 7730 + +Сверено **30 октября 2026**: [#7730](https://github.com/duckduckgo/apple-browsers/pull/7730) снова **OPEN**, не draft, **MERGEABLE**, head **`cf668e29382df79f1a94961e39067beac85c6128`**, ветка `alex/cpm-error6-restart`. Есть label **privacy review required**. Не merged и не shipped; локальный review и MERGEABLE не заменяют privacy review, CI или rollout. Историческое закрытие draft 28 октября больше не является текущим статусом. + +**Что делает фикс.** Для новой error 6 текущего embedded context manager запоминает исходный failed background view. Если к выполнению recovery WebKit всё ещё держит именно его, выполняется full embedded-context restart с trigger `cpmBackgroundLoadFailure`; при освобождённом view остаётся обычный P2 background-only retry. Новый replacement view, даже ещё загружающийся, не перезапускается. Дополнительного page reload/re-init фикс не добавляет; в контролируемом harness исходный документ получает настоящий `initResp` после restart. + +**Background/wake и очередь.** Попытка хранит context/view слабо. Новая ошибка заменяет pending-попытку также в foreground, если прежняя recovery ещё стоит в очереди. На iOS отложенная recovery получает возможность на `didBecomeActive`, на macOS — на `didWake`; повторные события не воспроизводят завершённую попытку. Cooldown **60 s** отсчитывается от фактически применённой recovery: ошибка сохраняется до истечения срока и запускается таймером без новой навигации/notification. Если срок истёк в background, попытка ждёт foreground. Перед выполнением снова проверяются актуальность context/view, runtime flags и concurrent-recovery guard. Исторические равные errors не запускают restart; история сбрасывается при создании нового background view, чтобы задержанные clear/add notifications не скрыли новую одинаковую error 6. + +**Flags.** `webExtensions.cpmBackgroundLoadRecovery` разрешает P2/recovery целиком; отдельный `cpmBackgroundLoadRestart` — только retained-view restart, code default ON на iOS/macOS, default OFF в manager API. OFF у restart сохраняет plain retry. M4 использует собственный `cpmMessagingHangRecovery`. [Registry task](https://app.asana.com/1/137249556945/project/1211834678943996/task/1219970564251181); [задача PR](https://app.asana.com/1/137249556945/project/72649045549333/task/1219939142983214?focus=true). Live remote config этой работой не менялась и не проверялась. + +**Телеметрия реализована.** Новый `web_extension_cpm_background_load_restart` имеет одно имя на iOS/macOS, **без `_macos`**, частоту dailyAndCount и единственный custom parameter `outcome`. Stages: `restart_started`, `background_ready`; terminal outcomes: `response_received`, `no_response_before_deadline`, `unobserved`, `superseded`, `reload_failed`, `reload_timed_out`. Наблюдение отдельно от failure episode: быстрый restart может завершиться раньше первого navigation timeout. Успех требует атрибутируемого dashboard response нужного поколения; background callback — только промежуточный этап. Deadline — пять активных минут, suspension исключается, после resume остаётся минимум 30 s. Без измеренного timeout/ответа — `unobserved`; другой reload или загрузка новой embedded-версии завершает старое наблюдение как `superseded`. Повторный `loaded` того же identifier и другой extension type его не закрывают. Ошибка reload уже даёт `reload_failed` и существующий reload-error pixel с `cpm_background_load_failed`. URLs, tab IDs и raw errors новым pixel не отправляются. Для outcome rates использовать **count**, не daily: daily dedupe работает по имени и обычно оставляет первый `restart_started`. Отсутствие terminal row после завершения процесса не считать failure. + +**Проверки на текущем head.** **1064 штатных WebExtensions tests PASS**, strict SwiftLint и pixel-schema/format/wide-event checks обеих платформ PASS. Финальный source review: **0 существенных findings**; отдельный полный прогон со **8 временными edge-case probes — 1072 PASS / 0 failures**. Probes проверили burst ошибок, повторный background перед queued recovery, смену context/flags во время cooldown, upgrade после measured failure и позднее завершение прежнего reload. Временные probes удалены; постоянные manager, monitor и platform-adapter regressions входят в обычный PR CI. В unit-регрессии без cooldown timer автоматический retry действительно падает. Exact-head CI заново в этом обновлении документации не запрашивался. + +**Harness отдельно от main.** Production worktree `/Users/admin/DuckDuckGo/apple-browsers.git/cpm-error6-restart`; review harness — `cpm-error6-restart-harness`, ветка [`codex/cpm-error6-restart-harness`](https://github.com/duckduckgo/apple-browsers/tree/codex/cpm-error6-restart-harness), опубликованный head **`5348c7254efb23e44a4f53e5150dc95cc5edd938`**, синхронизирован с `cf668e2938`. Отличия — только **17 fixture/test Swift files + run guide**, production code идентичен. [Инструкции OFF/ON](https://github.com/duckduckgo/apple-browsers/blob/codex/cpm-error6-restart-harness/SharedPackages/WebExtensions/CPMError6RestartHarness.md). + +Последний реальный macOS OFF/ON выполнен **на production `d1a2e1de65`**, а не повторно на финальном `cf668e2938`: одинаковый binary SHA256 `5369bd0105cc8c1f8257db374a631cae0a4f6146ca038e1e0aa4c75da6f2d141`. OFF — ожидаемый functional FAIL: настоящая error 6, retained=true, исходный timeout, потерянный `busy 0`, restarts/responses=0; позднее помогает M4. ON — PASS: настоящий dashboard response и page `initResp`, первый ответ 0.084 s, lost=[], restarts=1, responses=1, два следующих idle/wake цикла здоровы. Более поздние queue/cooldown/upgrade-attribution изменения проверены unit/regression tests, не новым OFF/ON запуском. + +**Границы.** Natural production trigger и доля retained-view failures неизвестны; новые production metrics не запрашивались. Контролируемый worker-start exception доказывает этот механизм, не всю iOS interruption/error-6 когорту. Физический iOS suspend/wake финальным прогоном не проверен. Старый draft использовал full reload шире; нынешний restart ограничен тем же retained failed view. Не переносить прежний отрицательный результат full-context P2 из 16 октября на этот более узкий случай как доказательство неработоспособности; он остаётся историческим ограничением, а положительный harness — отдельным контролируемым доказательством. + +Артефакты: `cpm-error6-verification-2026-10-30/final-review/{verification.json,tests.log}`, `pending-recovery/`, `foreground-publish/` в корне bare repo; финальный штатный run и validators — `/private/tmp/cpm-upgrade-observation-tests.log`, `cpm-upgrade-observation-lint.log`, `cpm-upgrade-{iOS,macOS}-{validate-pixel-defs,check-wide-events}.log`. Карта кода — [[cpm-web-extension-code-guide-2026-10-09#Error-6 restart — PR 7730]]; reviewer steps — [[CPM Breakage Testing Steps#Error-6 restart — PR 7730 (30 October 2026)]]. + +## Текущее состояние — 30 октября 2026, 16:48 Бишкек + +### Upgrade-before-load: PR открыт, CI зелёный + +[#7767 — Upgrade CPM before loading installed extensions](https://github.com/duckduckgo/apple-browsers/pull/7767), ветка `codex/cpm-upgrade-before-load`, опубликованный head **`45593bbb4e631b5daae9ed77e95f4bd760f738fa`**, base `main` **`3046f67631d5655f227cd2e7e7ebf4ef60d0d90a`**. Актуальный main влит в ветку PR; конфликт stable-origin policy устранён с сохранением OS 26.6+ gate и pending-origin cleanup/rotation. PR **OPEN, MERGEABLE**, не merged и не shipped. Проверено на этом head: **43 checks SUCCESS, 15 SKIPPED, 0 failed/pending**; итоговые iOS/macOS/BSK/DBP, Danger и SwiftLint — SUCCESS. `mergeStateStatus=BLOCKED` при зелёных checks не означает merge conflict. + +**Порядок запуска:** при включённом `cpmUpgradeBeforeLoad` coordinator сначала подготавливает только CPM (`.embedded`), затем загружает установленные extensions, затем выполняет обычный общий sync. Предварительная фаза обновляет/устанавливает включённый CPM либо удаляет выключенный, не запускает scriptlet handling и не синхронизирует остальные bundled types. Другие contexts ждут завершения этой подготовки, но для них сохраняется собственный порядок load → sync. Scriptlet downloads начинаются после загрузки contexts; зависшая загрузка ad-block scriptlets не держит их незагруженными. + +**Fallback и совместимость:** новая версия устанавливается до удаления старой; ошибка pre-load upgrade оставляет прежнюю установку для следующего load. Обычный post-load sync затем может повторить попытку. Если повреждённая current-version установка была пропущена pre-load, load удаляет её и общий sync восстанавливает в том же запуске. При выключенном флаге действует прежний порядок: load установленной версии → общий sync/upgrade. + +**Origin:** переносится только для CPM при включённом upgrade-before-load, включённой stable-origin policy и отсутствии загруженного старого context. Для присвоения stable baseURL также нужны OS 26.6+ и приватный CPM manifest без web-accessible resources. Runtime upgrade или post-load retry при живом старом context использует отдельные origins, потому что установка ненадолго загружает обе версии. Ротации после background failures сохраняются; неподходящий manifest очищает унаследованный pending origin, а отключённая policy/неподдерживаемая ОС не сохраняет abandoned origin. JSON key `cpmStableOriginIdentifier` и installation-UUID fallback из main сохранены. + +**Откат:** `webExtensions.cpmUpgradeBeforeLoad` — remote-releasable failsafe, code default ON на iOS/macOS. [Feature Flag Registry](https://app.asana.com/1/137249556945/project/1211834678943996/task/1220009029295774). Значение фиксируется при создании manager; изменение применяется к следующему manager, обычно после перезапуска приложения. OFF возвращает старый launch order и прекращает перенос origin в upgrade. Отдельный `cpmStableBaseURL` по-прежнему управляет stable origin. Live remote configuration в этом обновлении не менялась и не проверялась. + +### Диагностика failures без нового пикселя + +Используется существующий `embeddedInstallError` / CPM install-error pixel обеих платформ. Только ошибка CPM pre-load sync получает суффикс **`.before_load` в error domain**; code, userInfo и доступная underlying-error chain сохраняются. Обычный post-load sync передаёт исходную ошибку без суффикса. Новых success/outcome pixels, stage parameters, таймеров и launch tracking нет. + +Отсутствующий bundle и несовпадение типа теперь тоже сообщаются через существующий error pixel: `EmbeddedSyncError: DDGError`, domain `WebExtensionEmbeddedSync`, codes **1 = missingBundle, 2 = typeMismatch**. `typeMismatch` перехватывается внутри sync и не прерывает загрузку остальных типов. Ошибки WebKit нормализуются в обёртках самих WebKit API; повторный `webKitSafe` в telemetry helper удалён. Для изменения domain используется `error as NSError`. + +Ранее выполненный ClickHouse-срез за **29 октября UTC**, без суммирования standard и daily legs: **518 iOS / 16 macOS install-error events** по всем версиям; на macOS 14 из 16 — invalid archive (`WKWebExtensionErrorDomain:9`), на iOS преобладали storage и invalid-archive errors. Это события ошибок, не affected-user rate. Проверка корреляции с CPM failure episodes **не установила причинную связь**: общих installation/session IDs нет, а почасовые totals зависят от трафика. `extension_updated` в истории означает успешное обновление и само по себе не связывает episode с install error. Этот срез не перезапрашивался при обновлении документации; он предшествует rollout нового domain suffix. + +### Проверки, производительность и границы + +- После первого merge main (`f479f5deb2`): **1037 package tests / 0 failures**, shared iOS Simulator compilation и strict lint PASS. +- На опубликованном `fafcb1ffda`: **11 real-WebKit harness scenarios / 0 failures / 0 skips**, включая fallback, flag OFF, disabled CPM, runtime upgrade, повторные launch upgrades и persistent profile с 2000 cookie-bearing sites. Это package harness, не full-app E2E. +- После перехода на DDGError и исправления теста: **89 manager tests PASS**, strict lint PASS. Тест телеметрии упал в iOS CI из-за обращения к отсутствующему `Bundle.module`; `00ddded2ff` заменил настоящий CPM archive минимальным ZIP fixture, создаваемым тестом. Исправленный тест реально прошёл на **iOS 27 Simulator**, в том числе при намеренно убранном resource bundle. В production code ради этого падения ничего не менялось. Новый файл тестов не добавлялся; сохранён один telemetry regression method. +- На последнем merge `45593bbb4e` — зелёный exact-head CI, как указано выше. Исторические локальные counts относятся к своим heads; это не новые прогоны на последнем merge. + +**Измерение 29 октября:** по 20 launches на сценарий, три документа на launch. Медиана времени до ответов всех трёх документов: прежний load-before-sync — **180.9 / 173.6 / 208.3 / 205.8 ms**; последний CPM-only sample — **181.7 / 172.3 / 179.5 / 178.4 ms** для first install / unchanged / upgrade / upgrade + 2000 sites. Во всех 240 measured replies последней серии — 0 потерь, один context. Это отдельные harness samples; в них не видно регрессии первого/обычного запуска, но они не измеряют полный старт приложения, большой HTTP cache или production hang rate. Сырые результаты: `/Users/admin/DuckDuckGo/apple-browsers.git/cpm-upgrade-before-load-results-2026-10-29/`, `summary.json`, `cpm-only-summary.json`, `CPM_ONLY_REVIEW.md`. + +**Что остаётся неизвестным:** production-доля failures, устраняемых новым порядком; эффект после rollout; полный iOS app startup/upgrade runtime и full-app UI этого PR локально не проверялись. iOS Simulator telemetry test не является таким app-level прогоном. Error-page fix #7752/#7753 уже merged в release branches, но macOS wake/other baseline до него может содержать false positives. iOS полезен как отдельный срез, однако его нельзя считать доказанно незагрязнённым контролем без соответствующей runtime-проверки. Сравнивать платформы, версии, ОС и rollout/flag states отдельно; новые production metrics в этом обновлении не получались. + +Код и worktree: [[cpm-web-extension-code-guide-2026-10-09#Upgrade-before-load — PR 7767]]. Приоритеты и границы старого production baseline: [[cpm-post-release-analysis-2026-10-27#Upgrade-before-load — 30 October 2026]]. + +## Обновление 30 октября 2026 — release PR merged + +[iOS #7753](https://github.com/duckduckgo/apple-browsers/pull/7753) merged в `release/ios/7.244.0`, [macOS #7752](https://github.com/duckduckgo/apple-browsers/pull/7752) — в `release/macos/1.214.0`. Последние публичные GitHub-релизы при проверке 30 октября — 7.243.0 / 1.213.0: merge не означает rollout или подтверждённый эффект. + +Итог: фильтр error pages; stable baseURL только на ОС 26.6+ при включённом флаге; при создании контекста на неподдерживаемой ОС или с выключенным флагом сохранённый origin ротируется, а WebKit получает `nil`. Само переключение флага живой контекст не меняет. Ключ JSON изменён с `cpmOriginIdentifier` на `cpmStableOriginIdentifier`; миграции, автозаполнения UUID и хранения версии ОС нет. Без нового ключа используется уже сохранённый installation UUID, который далее переиспользуется; обычная ротация сохраняет замену под новым ключом. + +Невосстановимая поломка после обновления ОС из-за сохранённого baseURL не доказана. В двухпроцессном baseline перезапуск восстановил messaging даже с прежним origin. Историческое утверждение «extension workers никогда не писались на диск» исправлено: upstream запретил новые записи, но этим изменением старые не удаляются. + +Полный итог, проверенные тесты, merge commits и OS-разрез данных 26–28 октября: [[cpm-post-release-analysis-2026-10-27#Release update — 30 October 2026]]. Ниже остаются датированные исторические срезы; их open/shipped-статусы не заменяют это обновление. + + > **P5 #7473 — merged 20 октября 2026, 14:17:11 Бишкек (08:17:11 UTC):** [merge `9bcce0264e`](https://github.com/duckduckgo/apple-browsers/commit/9bcce0264e60ef79b98c1b8aeb23ef9f26c45bc0) в `release/macos/1.213.0`, финальный PR head `574e9288ae`. Фикс shared-pinned window close включён по умолчанию и имеет remote failsafe. Подробности реализации, review и проверок — [[cpm-status-2026-10-15#P5 — merged 20 октября 2026]]. Merge в release branch подтверждён; публикация сборки, rollout и production effect этим не установлены. Эта сверка касается #7473. > **#7379 — независимый P2 flag, 20 октября 2026:** production `55a42802a6`, harness `a6e6835a5f`; новый `webExtensions.cpmBackgroundLoadRecovery` **default ON на iOS/macOS** отделяет same-context error-6 retry от `cpmMessagingHangRecovery` (full-context restart). **271 selected unit PASS**, 47 новых shared regression methods; четыре новых independence regressions дают 4 FAIL с прежними связанными проверками и PASS с новым flag. Signed macOS harness и полный iOS app build PASS. CI нового head: 39 SUCCESS / 13 SKIPPED / 4 IN_PROGRESS, без failures (20 октября, 14:16 UTC). Late GET reload по-прежнему default OFF; P4 detection независим. Подробности — в последнем разделе про independent P2 flag. diff --git a/work/projects/cpm-web-extension-breakage-findings.md b/work/projects/cpm-web-extension-breakage-findings.md index 94d242b4..9b6b2f5b 100755 --- a/work/projects/cpm-web-extension-breakage-findings.md +++ b/work/projects/cpm-web-extension-breakage-findings.md @@ -7,10 +7,14 @@ tags: - cpm - webkit - web-extension -updated: '2026-10-20' +updated: '2026-10-30' --- # CPM Web Extension Breakage Findings +> **Error-6 restart — 30 октября 2026:** [#7730](https://github.com/duckduckgo/apple-browsers/pull/7730) снова OPEN, head `cf668e2938`, с privacy-review gate; не merged/shipped. Реализованы retained-view restart, отложенная foreground/wake recovery, сохранение ошибки через cooldown и отдельный outcome pixel. Финальный source review не нашёл существенных проблем: 1064 штатных tests + 8 временных probes PASS. Natural trigger и физический iOS suspend/wake не установлены. Текущий контракт, проверенные heads и границы — [[cpm-status-2026-10-15#Error-6 restart — PR 7730]]. Датированные выводы ниже остаются историей. + +> **Актуализация 30 октября 2026:** #7752 / #7753 merged в release 1.214 / 7.244; публикация и эффект ещё не подтверждены. Только rename JSON key, без миграции UUID и OS-version tracking; без нового ключа используется installation UUID. Ошибочное утверждение ниже о том, что extension workers никогда не сохранялись, исправлено. Текущий итог и границы доказательств: [[cpm-post-release-analysis-2026-10-27#Release update — 30 October 2026]]. Остальные датированные разделы сохраняют историю исследования. + > **Актуальный контекст — 20 октября 2026.** Это исторический журнал source audit, ручных repro и отозванных гипотез. Его «нет фикса», local-only, bundle/OS и CI states относятся к датам записей; текущие heads и gates — [[cpm-status-2026-10-15#2. PR и quality gates]]. > > Опубликованы отдельные реализации P1/P2/P4 (#7379), Network recovery (#7459), P3 stable origin (#7493) и shared-pinned P5 (#7473). **#7473 merged 20 октября в `release/macos/1.213.0` (`9bcce0264e`)**, с default-ON failsafe; детали и отдельные app tests — [[cpm-status-2026-10-15#P5 — merged 20 октября 2026]]. Merge не доказывает rollout, shipped effect или всю production causal share. #7376 с единым episode report закрыт без merge. Его `bg_events` теперь продолжается до первого stuck, имеет `;`/`...`, но общий budget может вытеснить initial history. Реальная потеря начала подтверждена агрегатами production (§6 основного статуса). @@ -270,7 +274,7 @@ The following ideas do not yet have a traced path into a stable stuck state: They may alter timing or storage pressure, but should not be presented as reproduction scenarios without a corresponding failing handler sequence or production trace. -Safari 26.6 includes a fix for service-worker registration database files accumulating on launch, and WebKit has also fixed cold-launch cleanup that delayed extension content injection. **[corrected]** The registration-database fix is irrelevant here: extension service workers are never persisted (`SWServerWorker::shouldPersistToDisk` returns false for service-worker-page registrations) and the registration is cleared whenever the SW page client goes away. +**Correction, 30 October 2026:** the earlier “extension service workers are never persisted” statement was too broad. [WebKit commit eff9f9231ad90e31e6ae2b128d2269c589550c8d](https://github.com/WebKit/WebKit/commit/eff9f9231ad90e31e6ae2b128d2269c589550c8d) (312316@main, 29 April 2026, “Stop persisting service workers from browser extensions to disk”) adds a filter for new persistence writes; that change does not purge old registration records. Older WebKit could persist them. This establishes possible historical records, not a reproduced permanent breakage after an OS upgrade. A live-session background-process termination reproduced an error-6 loop recoverable by changing origin, whereas the real two-process restart baseline restored messaging even with the previous origin reused. See `alex/cpm-p3-integration-tests/macOS/IntegrationTests/CPM/README.md` and [[cpm-post-release-analysis-2026-10-27#Stable-origin evidence and limits]]. Avoid inferring either “old records cannot exist” or “saving the origin permanently breaks upgrading users” from the current evidence. ## Evidence needed next diff --git a/work/projects/cpm-web-extension-breakage-validation.md b/work/projects/cpm-web-extension-breakage-validation.md index c6d5300f..5613e420 100755 --- a/work/projects/cpm-web-extension-breakage-validation.md +++ b/work/projects/cpm-web-extension-breakage-validation.md @@ -1,8 +1,24 @@ --- -updated: '2026-10-20' +updated: '2026-10-30' --- # CPM Web Extension Breakage — статус и проверка причин +## Текущее состояние — 30 октября 2026 + +**Error-6 restart:** [#7730](https://github.com/duckduckgo/apple-browsers/pull/7730), head `cf668e2938`, OPEN/MERGEABLE, privacy review required, не merged/shipped. На этом head 1064 штатных WebExtensions tests PASS; финальный review с 8 временными edge-case probes — 1072 PASS, без существенных findings. Реальный macOS OFF/ON подтверждён на более раннем `d1a2e1de65`; физический iOS suspend/wake и natural production trigger не проверены. Полное текущее поведение, flags, pixel outcomes, очередь/cooldown и ссылки на артефакты: [[cpm-status-2026-10-15#Error-6 restart — PR 7730]]. Ниже — отдельный upgrade-before-load PR и исторические validation snapshots. + +Upgrade-before-load реализован в [PR #7767](https://github.com/duckduckgo/apple-browsers/pull/7767), head `45593bbb4e`, main `3046f67631` влит. PR OPEN/MERGEABLE; exact-head CI **43 SUCCESS / 15 SKIPPED / 0 failed/pending**. Не merged и не shipped. + +Подготовка до load применяется только к CPM, без scriptlet downloads. Ошибка upgrade оставляет старую установку для load и последующего retry. `webExtensions.cpmUpgradeBeforeLoad` — default-ON failsafe, применяется при создании manager; OFF возвращает прежний порядок на следующем manager/запуске. Origin reuse дополнительно зависит от stable-origin policy, OS 26.6+, приватного manifest и отсутствия живого старого context. + +Проверки относятся к своим revisions: 1037 package tests после первого main merge; 11 real-WebKit upgrade/fallback/flag/profile scenarios на `fafcb1ffda`; 89 manager tests после финального telemetry fixture fix. Единственное iOS CI падение было в тесте из-за отсутствующего resource bundle. После `00ddded2ff` тест создаёт собственный ZIP и проходит на iOS 27 Simulator также при удалённом bundle. Full-app startup/upgrade на iOS локально не проверялся. + +Нового failure/success pixel нет: существующий install-error pixel получает domain suffix `.before_load` только для CPM pre-load errors; code/userInfo/доступная underlying chain сохранены. Missing bundle/type mismatch — `DDGError`, коды 1/2. + +Актуальные детали, baseline/after timings и ограничения: [[cpm-status-2026-10-15#Текущее состояние — 30 октября 2026, 16:48 Бишкек]]. Приоритеты с учётом error-page false positives: [[cpm-post-release-analysis-2026-10-27#Upgrade-before-load — 30 October 2026]]. Точки входа и рабочие копии: [[cpm-web-extension-code-guide-2026-10-09#Upgrade-before-load — PR 7767]]. + +Ниже сохранены датированные прежние состояния PR, тестов и планов; их OPEN/CI/«не опубликовано» не описывают текущий #7767. + > **Сверено 20 октября 2026.** Актуальный реестр PR/CI — [[cpm-status-2026-10-15#2. PR и quality gates]], телеметрия/границы диагностики — §6, исходники — [[cpm-web-extension-code-guide-2026-10-09#0. Текущая карта worktree и точек входа]]. > > #7376 `5d1cb4abd2` опубликован, CI зелёный, но закрыт без merge. #7484 — старый macOS payload с conflict. #7379 имеет failed BSK crash gate; #7459 и #7493 имеют зелёный exact-head CI. **P5 #7473 merged 20 октября в `release/macos/1.213.0` (`9bcce0264e`)**, CI финального head `574e9288ae` зелёный; default-ON failsafe сохранён. Unit tests проверяют WebKit `openTabs`; real-window fixture остаётся вне merge. Проверенный ON/OFF/ON и границы — [[cpm-status-2026-10-15#P5 — merged 20 октября 2026]]. Публикация/rollout ещё не установлены. Новая телеметрия измеряет completed episodes, не совпадает со старой системой event totals. diff --git a/work/projects/cpm-web-extension-code-guide-2026-10-09.md b/work/projects/cpm-web-extension-code-guide-2026-10-09.md index d382174b..0db9c737 100755 --- a/work/projects/cpm-web-extension-code-guide-2026-10-09.md +++ b/work/projects/cpm-web-extension-code-guide-2026-10-09.md @@ -5,10 +5,54 @@ tags: - work - cpm - webkit -updated: '2026-10-20' +updated: '2026-10-30' --- # CPM Web Extension — карта кода по worktree и строкам +## Error-6 restart — PR 7730 + +Карта на **30 октября 2026**, production head **`cf668e29382df79f1a94961e39067beac85c6128`**, [PR #7730](https://github.com/duckduckgo/apple-browsers/pull/7730) OPEN/MERGEABLE, не merged/shipped. Ветка `alex/cpm-error6-restart`, worktree `/Users/admin/DuckDuckGo/apple-browsers.git/cpm-error6-restart`. + +- `SharedPackages/WebExtensions/Sources/WebExtensions/Manager/WebExtensionManager.swift:408` — error notification observer, multiset-diff новых error 6; `:1062` — weak snapshot failed context/view; `:1087` — очередь последней попытки и runtime guards; `:1138` — одноразовый cooldown timer; `:1161` — retained-view context restart. `:1604` сбрасывает историю при создании нового background view. `:1035` отправляет промежуточный background_ready. +- `Autoconsent/CPMInterruptionObserver.swift:51` — iOS didBecomeActive для recovery, `:57` — macOS didWake. Health-monitor clocks возобновляются отдельно на iOS willEnterForeground. +- `Autoconsent/CPMMessagingHealthMonitor.swift:197` — отдельное restart observation; `:933` — lifecycle attribution, включая superseded на loaded другого embedded identifier; `:979` — единственный terminal outcome и отмена deadline; `:998` — quiet deadline. Same-identifier loaded и другой extension type сохраняют наблюдение. +- `Pixels/WebExtensionPixelFiring.swift:261` — bounded outcomes; `:289` — canonical metadata для `web_extension_cpm_background_load_restart`, dailyAndCount, только custom `outcome`, без `_macos`. Platform adapters и JSON5 definitions есть на обеих платформах. +- `WebExtensionManagerTests`, `CPMInterruptionObserverTests`, `CPMMessagingHealthMonitorTests` — постоянные queue/cooldown/wake, historical/equal-error и upgrade-attribution regressions; native PixelKit adapter tests находятся в iOS/macOS app test targets. Shared package run: 1064 PASS; final review run с 8 временными probes: 1072 PASS. Это не full-app iOS suspend/wake прогон. + +Review-only worktree `/Users/admin/DuckDuckGo/apple-browsers.git/cpm-error6-restart-harness`, branch `codex/cpm-error6-restart-harness`, head **`5348c7254efb23e44a4f53e5150dc95cc5edd938`**; production code совпадает с `cf668e2938`, diff — ровно 17 Swift fixtures/tests + `SharedPackages/WebExtensions/CPMError6RestartHarness.md`. Последний реально выполненный macOS OFF/ON относится к `d1a2e1de65`; не приписывать его повторный запуск текущему head. Reviewer guide: [CPMError6RestartHarness.md](https://github.com/duckduckgo/apple-browsers/blob/codex/cpm-error6-restart-harness/SharedPackages/WebExtensions/CPMError6RestartHarness.md). + +Флаги, telemetry interpretation, доказательства и ограничения — [[cpm-status-2026-10-15#Error-6 restart — PR 7730]]. Состояние upgrade-before-load #7767 ниже относится к отдельному PR. + +## Upgrade-before-load — PR 7767 + +Проверено 30 октября 2026, 16:48 Бишкек: [#7767](https://github.com/duckduckgo/apple-browsers/pull/7767), production worktree **`/Users/admin/DuckDuckGo/apple-browsers.git/cpm-upgrade-before-load`**, branch `codex/cpm-upgrade-before-load`, head **`45593bbb4e`**. Main `3046f67631` влит; PR OPEN/MERGEABLE, exact-head CI 43 SUCCESS / 15 SKIPPED, 0 failed/pending. Не merged и не shipped. + +- `SharedPackages/WebExtensions/Sources/WebExtensions/Lifecycle/WebExtensionLifecycleCoordinator.swift`, `loadAndSyncInstalledExtensions()`: CPM pre-sync при включённом failsafe → load installed → общий embedded sync. +- `Manager/WebExtensionManager+EmbeddedExtensions.swift`, `syncCPMExtensionBeforeLoad(isEnabled:)`: только `.embedded`; не запускает scriptlets; disabled CPM удаляется до load. `syncEmbeddedExtension`/`installEmbeddedExtension`: install before uninstall, fallback к старой установке; origin переносится только без живого старого context. Runtime/post-load upgrades сохраняют разные origins. +- `Loading/CPMExtensionBaseURLPolicy.swift`: `originForUpgrade`, `pendingOriginIdentifiers`, `baseURL(for:webExtension:)`. При объединении с main сохранены OS 26.6+ gate, rotation на disabled/unsupported context load и отказ от inherited origin при публичных ресурсах. Persisted key — `cpmStableOriginIdentifier`. +- `Manager/WebExtensionManager.swift`, `isCPMUpgradeBeforeLoadEnabled`: значение фиксируется при создании manager. Обе платформенные factory передают `.cpmUpgradeBeforeLoad`; конфигурации в `iOS/LocalPackages/FeatureFlags-iOS/Sources/FeatureFlags/FeatureFlag.swift` и `macOS/LocalPackages/FeatureFlags-macOS/Sources/FeatureFlags/FeatureFlag.swift`: code default ON, remote failsafe `webExtensions.cpmUpgradeBeforeLoad`. [Registry task](https://app.asana.com/1/137249556945/project/1211834678943996/task/1220009029295774). OFF применяется к следующему manager. +- `fireEmbeddedSyncError` в embedded-manager extension: существующий error pixel; CPM pre-load domain получает `.before_load`, остальной sync — исходную ошибку. `EmbeddedSyncError: DDGError`: missingBundle=1, typeMismatch=2. Нового pixel/stage parameter/success tracking нет. WebKit error normalization остаётся в `WKWebExtension+CopyingErrors.swift`; в helper достаточно `error as NSError`. +- `Tests/WebExtensionsTests/WebExtensionManagerTests.swift`, `testCPMSyncErrorMarksOnlyBeforeLoadAndPreservesCause`: один telemetry regression method, domain/code/userInfo/underlying cause и missing-bundle DDGError. После iOS CI crash тест создаёт собственный ZIP через существующий helper, без `Bundle.module`. Проверен на iOS 27 Simulator также без resource bundle. +- Review-only harness — `/Users/admin/DuckDuckGo/apple-browsers.git/cpm-upgrade-before-load-harness`, `Tests/WebExtensionsTests/CPMRepro/CPMReproUpgradeTests.swift`. Это отдельный исторический checkout, не текущий head PR. Финальный опубликованный `fafcb1ffda` был отдельно проверен с копией harness: 11 PASS, после проверки временная копия удалена. Для воспроизведения с новой production revision fixture переносится в изолированный checkout, без включения в PR. +- Артефакты before/after: `/Users/admin/DuckDuckGo/apple-browsers.git/cpm-upgrade-before-load-results-2026-10-29/` (`summary.json`, `cpm-only-summary.json`, `CPM_ONLY_REVIEW.md`). Более поздние логи: `/private/tmp/cpm-7767-main-merge-tests.log`, `cpm-7767-full-review-head-harness.log`, `cpm-7767-ci-fixture-macos.log`, `cpm-7767-ci-fixture-ios-no-bundle.xcresult`. + +Поведение, сравнение времени и границы доказательств: [[cpm-status-2026-10-15#Текущее состояние — 30 октября 2026, 16:48 Бишкек]]. Runtime iOS-проверка здесь относится к telemetry test, а не к полному startup/upgrade приложения. Следующее обновление ниже относится к release error-page/stable-origin PRs, не к #7767. + +## Актуализация 30 октября 2026 + +Release PR merged: [iOS #7753](https://github.com/duckduckgo/apple-browsers/pull/7753), head `fef5f9cdd35977b0cb848ac81f4feaca3e6a9ba6`, worktree `cpm-error-page-filter-ios`; [macOS #7752](https://github.com/duckduckgo/apple-browsers/pull/7752), head `551cb2edd00c02e038b2b46a07a8c32d0b33e666`, worktree `cpm-error-page-filter`. + +Актуальные точки входа: +- `SharedPackages/WebExtensions/Sources/WebExtensions/Loading/InstalledWebExtension.swift`: `CodingKeys.cpmOriginIdentifier = "cpmStableOriginIdentifier"`; старый ключ игнорируется. Миграции и OS-version marker нет. +- `Loading/CPMExtensionBaseURLPolicy.swift` в том же модуле: installation-UUID fallback; stable URL при OS 26.6+ и включённом флаге; иначе ротация сохранённого origin и `nil` при создании контекста. Обычная ротация сохраняет новый UUID. +- `Autoconsent/CPMMessagingHealthMonitor.swift`: `.errorPage` исключён из измерения commit/finish. +- `macOS/DuckDuckGo/Tab/TabExtensions/PrivacyDashboardTabExtension.swift`: `.alternateHtmlLoad` → `.errorPage`. +- `CPMExtensionBaseURLTests.testUnsupportedOSReturnsNilAndRotatesSavedOrigin`: проверяет nil и замену сохранённого UUID через injected OS predicate. Финальная policy suite: 25 PASS; полный package run key-only revision: 1,025 PASS. +- `macOS/IntegrationTests/Tab/CPMErrorPageMonitoringTests.swift`: DNS, SSL, malicious warning; три PASS на исходной release-base реализации, отрицательный контроль без mapping падает. Shared-mock cleanup отложен на main; iOS runtime не проверялся. + +Статус публикации, ограничения доказательств и поведение при первом stable load: [[cpm-post-release-analysis-2026-10-27#Release update — 30 October 2026]]. Нижние ссылки на старые ветки/строки — исторические срезы, не актуальный release diff. + + > **#7379 — независимый P2 flag, 20 октября 2026:** production `55a42802a6`, harness `a6e6835a5f`; новый `webExtensions.cpmBackgroundLoadRecovery` **default ON на iOS/macOS** отделяет same-context error-6 retry от `cpmMessagingHangRecovery` (full-context restart). **271 selected unit PASS**, 47 новых shared regression methods; четыре новых independence regressions дают 4 FAIL с прежними связанными проверками и PASS с новым flag. Signed macOS harness и полный iOS app build PASS. CI нового head: 39 SUCCESS / 13 SKIPPED / 4 IN_PROGRESS, без failures (20 октября, 14:16 UTC). Late GET reload по-прежнему default OFF; P4 detection независим. Подробности — в последнем разделе про independent P2 flag. **Проверено 20 октября 2026:** карта ниже отражает существующие worktree и опубликованные heads; старые line numbers и результаты в архивных разделах относятся к своим ревизиям. PR/CI — [[cpm-status-2026-10-15#2. PR и quality gates]]. Пользовательский dirty `Package.resolved` в telemetry worktree сохранён. @@ -22,7 +66,7 @@ updated: '2026-10-20' - **macOS telemetry:** `alex/cpm-telemetry-macos-release`, ветка `alex/cpm-messaging-readiness-telemetry-macos-release`, **`3fb13b81fe`**, #7484. Старый `was_stuck`, без двухфазного timeline; release-base conflict. Это не идентичная копия текущего #7376. - **P1/P2/P4:** `alex/cpm-fix-init-loss-production`, `alex/cpm-fix-init-loss`, **`674bb809f2`**, #7379 на telemetry branch. Главные точки — `Manager/WebExtensionManager.swift`, `Autoconsent/CPMBackgroundReadinessRecovery.swift` и `Pixels/WebExtensionPixelFiring.swift` внутри WebExtensions Sources. Independent `startup_response_missing` не зависит от default-OFF late reload; recovery outcomes не являются page ACK. CI BSK красный. Отдельный app harness — `alex/cpm-fix-p1-p2`, `codex/cpm-fix-init-loss-integration-tests`. - **Network recovery:** `alex/cpm-network-process-recovery`, **`f41c7b7047`**, #7459 на telemetry branch; [watch/restart](/Users/admin/DuckDuckGo/apple-browsers.git/alex/cpm-network-process-recovery/SharedPackages/WebExtensions/Sources/WebExtensions/Lifecycle/CPMNetworkProcessRecovery.swift), manager wiring и `CPMNetworkProcessRecoveryTests.swift`. Applied pixel в metadata/обоих definitions: daily+count, no custom params, только после принятого restart. Нет доказательства recovery и нет `network_replaced_since_view` в episode. Harness: `alex/cpm-network-process-integration-tests`, `codex/cpm-network-process-integration-tests`. -- **P3 stable origin:** `alex/cpm-p3-stable-base-url`, **`dd96018da9`**, #7493 на telemetry branch; production default ON, remote kill switch. Это уже опубликованный ограниченный CPM origin policy, не старый глобальный M2 prototype. Harness `alex/cpm-p3-integration-tests`, `codex/cpm-p3-integration-tests`; full P3 failure остаётся невоспроизведённым. +- **P3 stable origin (история):** исходная ветка `alex/cpm-p3-stable-base-url`, `dd96018da9`, #7493. Актуальный release diff — #7752 / #7753 (см. обновление 30 октября выше). В коде есть default-ON и remote kill switch; live remote config этим не проверен. Harness: `alex/cpm-p3-integration-tests`, `codex/cpm-p3-integration-tests`. Задержка при смене origin воспроизведена с реальным HTTP cache; доля production failures от этого механизма не доказана. - **P5 shared pinned close:** [#7473](https://github.com/duckduckgo/apple-browsers/pull/7473) **MERGED 20 октября 2026** в `release/macos/1.213.0`, [merge `9bcce0264e`](https://github.com/duckduckgo/apple-browsers/commit/9bcce0264e60ef79b98c1b8aeb23ef9f26c45bc0); финальный reviewed/tested head **`574e9288ae`**, worktree `alex/cpm-p5-pinned-window-close`. Default ON + remote failsafe, CI зелёный. Harness остаётся отдельно: `alex/cpm-p5-integration-tests`, `codex/cpm-p5-release-integration-tests` @ `add6e514fe`. Один доказанный tab-lifecycle mechanism; rollout не подтверждён. - **Большой rewrite:** `alex/cpm-episode-telemetry`, **`7e8128c94f`** — альтернативный prototype, **не** реализация #7376. Не читать `CPMEpisodeTelemetry.swift`/`CPMEpisodeSummary.swift` как текущий PR. - Старый самостоятельный harness #7378 и baseline #7384 закрыты без merge. Native progress #7385 и JS #3727 открыты и не встроены в #7376; end-to-end delivery требует обеих сторон и обновлённого extension bundle. diff --git a/work/swift-testing-migration.md b/work/swift-testing-migration.md new file mode 100644 index 00000000..ba7f3e48 --- /dev/null +++ b/work/swift-testing-migration.md @@ -0,0 +1,756 @@ +# Swift Testing migration: XCTest conversion backlog + +Snapshot 2026-10-21, branch `alex/swift-testing` on top of origin/main. First batch: PR [#7547](https://github.com/duckduckgo/apple-browsers/pull/7547) (9 files). + +Survey: 1590 XCTest unit-test files (UI/Integration tests excluded). **399** have no complicating markers (no expectations, setUp, custom base class, Combine/async, UserDefaults/FileManager/CoreData). **129** more are complicated only by `setUp`/`tearDown`, which maps to `init`/`deinit`. + +Survey script: `xct2testing.py` + heuristic flags; full output kept in this note so it survives the scratchpad. + +## Repo conventions learned +- Every `@Test` needs `@available(iOS 16, macOS 13, *)` and `.timeLimit(.minutes(1))`. +- Suites touching shared `UserDefaults` or other global state: `@Suite(.serialized)`. +- Files need an explicit `import Foundation` (XCTest used to re-export it). +- Swift Testing runs tests in parallel: temp files and defaults suites need unique names per test instance. +- PR CI runs package tests only through the app schemes (`macOS Browser`, `iOS Browser`) and the BSK package loop; `EventHubTests`, `SnapshotTestingSupportTests`, `UIComponentsTests` currently never run: the first two are listed in the schemes but Xcode drops them from the xctestrun ("isn't a member of the specified test plan or scheme"), the third is in no scheme. Candidate fix: add them to the package loop and paths filters in `.github/workflows/bsk_pr_checks.yml`. + +## Short list: suggested next batches + +Small pure-logic files (extensions, parsers, models), one PR per area, ~10 files each. + + +### macOS/UnitTests (17 files) +- `macOS/UnitTests/Common/Extensions/BundleExtensionTests.swift` (1 tests, 38 loc) +- `macOS/UnitTests/HomePage/ColorSchemeLosslessStringConvertibleExtensionTests.swift` (2 tests, 38 loc) +- `macOS/UnitTests/Common/FileSystem/EncryptionKeyGeneratorTests.swift` (2 tests, 40 loc) +- `macOS/UnitTests/Common/Extensions/WKWebsiteDataStoreExtensionTests.swift` (2 tests, 43 loc) +- `macOS/UnitTests/Statistics/ATB/AtbParserTests.swift` (3 tests, 47 loc) +- `macOS/UnitTests/TabExtensionsTests/SpecialErrorPageUserScriptTests.swift` (2 tests, 49 loc) +- `macOS/UnitTests/Subscription/URL+SubscriptionAttributionTests.swift` (3 tests, 50 loc) +- `macOS/UnitTests/ContentBlocker/AppPrivacyConfigurationTests.swift` (2 tests, 52 loc) +- `macOS/UnitTests/Common/Extensions/ArrayExtensionTests.swift` (7 tests, 58 loc) +- `macOS/UnitTests/WebExtensions/WebExtensionPixelTests.swift` (4 tests, 63 loc) +- `macOS/UnitTests/Tab/Model/TabContentDisplayTitleTests.swift` (7 tests, 63 loc) +- `macOS/UnitTests/Common/Extensions/HTTPCookieTests.swift` (1 tests, 66 loc) +- `macOS/UnitTests/TabBar/ViewModel/AdjacentItemEnumeratorTests.swift` (4 tests, 67 loc) +- `macOS/UnitTests/Feedback/QuickFeedback/AppVersionModelDistributionLabelTests.swift` (4 tests, 73 loc) +- `macOS/UnitTests/Promotions/PromoHistoryRecordTests.swift` (6 tests, 75 loc) +- `macOS/UnitTests/AppHealth/StartupMetricsBucketsTests.swift` (4 tests, 80 loc) +- `macOS/UnitTests/NavigationBar/NavigationBarUrlExtensionsTests.swift` (1 tests, 86 loc) + +### iOS/DuckDuckGoTests (16 files) +- `iOS/DuckDuckGoTests/SERPSettingsViewPageTests.swift` (1 tests, 32 loc) +- `iOS/DuckDuckGoTests/UnifiedToggleInput/UpdatedModelPickerFeatureTests.swift` (2 tests, 41 loc) +- `iOS/DuckDuckGoTests/UIFontExtensionTests.swift` (4 tests, 43 loc) +- `iOS/DuckDuckGoTests/HomePageDisplayDailyPixelBucketTests.swift` (1 tests, 48 loc) +- `iOS/DuckDuckGoTests/APIHeadersTests.swift` (2 tests, 51 loc) +- `iOS/DuckDuckGoTests/AppPrivacyConfigurationTests.swift` (2 tests, 52 loc) +- `iOS/DuckDuckGoTests/NetworkProtectionStatusViewTintSpecTests.swift` (1 tests, 56 loc) +- `iOS/DuckDuckGoTests/TabEvictionSettingsTests.swift` (2 tests, 56 loc) +- `iOS/DuckDuckGoTests/UnifiedToggleInput/MultiTabMentionTokenTests.swift` (3 tests, 56 loc) +- `iOS/DuckDuckGoTests/BackForwardMenuHistoryItemURLSanitizerTests.swift` (5 tests, 61 loc) +- `iOS/DuckDuckGoTests/Autocomplete/SuggestionListKeyboardSelectionTests.swift` (9 tests, 61 loc) +- `iOS/DuckDuckGoTests/SearchToken/SearchTokenExperimentSettingsTests.swift` (5 tests, 65 loc) +- `iOS/DuckDuckGoTests/AtbParserTests.swift` (6 tests, 66 loc) +- `iOS/DuckDuckGoTests/SettingsDeepLinkSectionTests.swift` (6 tests, 66 loc) +- `iOS/DuckDuckGoTests/Subscription/URL+QueryParametersTests.swift` (9 tests, 70 loc) +- `iOS/DuckDuckGoTests/Subscription/AsyncHeadlessWebViewSettingsTests.swift` (3 tests, 75 loc) + +### SharedPackages/BrowserServicesKit/Tests (13 files) +- `SharedPackages/BrowserServicesKit/Tests/PrivacyDashboardTests/PageSignalsSettingsTests.swift` (2 tests, 32 loc) +- `SharedPackages/BrowserServicesKit/Tests/DDGSyncTests/ScopedAccess/ScopedAccessKeyFactoryTests.swift` (2 tests, 41 loc) +- `SharedPackages/BrowserServicesKit/Tests/SubscriptionTests/Flows/Models/PurchaseUpdateTests.swift` (3 tests, 46 loc) +- `SharedPackages/BrowserServicesKit/Tests/ConfigurationTests/ConfigurationFileReadErrorTests.swift` (4 tests, 47 loc) +- `SharedPackages/BrowserServicesKit/Tests/SubscriptionTests/API/Models/SubscriptionEntitlementTests.swift` (2 tests, 48 loc) +- `SharedPackages/BrowserServicesKit/Tests/PrivacyConfigTests/InternalUserDecider/DefaultInternalUserDeciderTests.swift` (4 tests, 52 loc) +- `SharedPackages/BrowserServicesKit/Tests/RemoteMessagingTests/Model/RangeStringMatchingAttributeTests.swift` (3 tests, 55 loc) +- `SharedPackages/BrowserServicesKit/Tests/MaliciousSiteProtectionTests/MaliciousSiteProtectionURLTests.swift` (3 tests, 58 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/SmarterEncryption/HTTPSUpgradeParserTests.swift` (6 tests, 68 loc) +- `SharedPackages/BrowserServicesKit/Tests/RemoteMessagingTests/Model/ArrayContainsAllMatchingAttributeTests.swift` (9 tests, 70 loc) +- `SharedPackages/BrowserServicesKit/Tests/RemoteMessagingTests/Matchers/MobileAppAttributeMatcherTests.swift` (5 tests, 80 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/LinkProtection/AMPKeywordSearchTests.swift` (7 tests, 81 loc) +- `SharedPackages/BrowserServicesKit/Tests/DDGSyncTests/ScopedAccess/AccountInfoKeyFactoryTests.swift` (3 tests, 87 loc) + +### SharedPackages/Infrastructure/SystemFrameworksExtensions (4 files) +- `SharedPackages/Infrastructure/SystemFrameworksExtensions/Tests/FoundationExtensionsTests/BundleExtensionTests.swift` (1 tests, 27 loc) +- `SharedPackages/Infrastructure/SystemFrameworksExtensions/Tests/FoundationExtensionsTests/LocaleExtensionsTests.swift` (3 tests, 43 loc) +- `SharedPackages/Infrastructure/SystemFrameworksExtensions/Tests/FoundationExtensionsTests/TimeIntervalConvenienceTests.swift` (6 tests, 48 loc) +- `SharedPackages/Infrastructure/SystemFrameworksExtensions/Tests/FoundationExtensionsTests/SequenceExtensionTests.swift` (6 tests, 56 loc) + +### SharedPackages/AIChat/Tests (4 files) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatModelLabelTests.swift` (1 tests, 29 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatModelTests.swift` (4 tests, 54 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/PageContextBlocklistSettingsTests.swift` (5 tests, 63 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatModelImageGenerationTests.swift` (3 tests, 78 loc) + +### SharedPackages/WebExtensions/Tests (3 files) +- `SharedPackages/WebExtensions/Tests/WebExtensionsTests/WebExtensionPixelExtensionTests.swift` (2 tests, 39 loc) +- `SharedPackages/WebExtensions/Tests/WebExtensionsTests/NativeMessagingFramingTests.swift` (5 tests, 68 loc) +- `SharedPackages/WebExtensions/Tests/WebExtensionsTests/WebExtensionAPICompatibilityLogTests.swift` (6 tests, 89 loc) + +### SharedPackages/VPN/Tests (3 files) +- `SharedPackages/VPN/Tests/VPNTests/NetworkProtectionDataVolumeBucketsTests.swift` (4 tests, 51 loc) +- `SharedPackages/VPN/Tests/VPNTests/TunnelConfigurationEndpointPortTests.swift` (2 tests, 55 loc) +- `SharedPackages/VPN/Tests/VPNTests/LeakCheck/IPAddressClassifierTests.swift` (12 tests, 80 loc) + +### SharedPackages/Common/Tests (2 files) +- `SharedPackages/Common/Tests/CommonTests/DecodableHelperTests.swift` (3 tests, 44 loc) +- `SharedPackages/Common/Tests/CommonTests/Extensions/StringExtensionTests.swift` (4 tests, 45 loc) + +### SharedPackages/DataBrokerProtectionCore/Tests (2 files) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/DataBrokerExecutionConfigTests.swift` (4 tests, 51 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/ServicesAuthHeaderBuilderTests.swift` (8 tests, 73 loc) + +### macOS/LocalPackages/AppKitExtensions (2 files) +- `macOS/LocalPackages/AppKitExtensions/Tests/AppKitExtensionsTests/NSMenuItemExtensionTests.swift` (2 tests, 56 loc) +- `macOS/LocalPackages/AppKitExtensions/Tests/AppKitExtensionsTests/AverageBrightnessTests.swift` (1 tests, 66 loc) + +### SharedPackages/Networking/Tests (2 files) +- `SharedPackages/Networking/Tests/NetworkingTests/v2/Extensions/HTTPURLResponseETagTests.swift` (5 tests, 67 loc) +- `SharedPackages/Networking/Tests/NetworkingTests/v2/Extensions/HTTPURLResponseCookiesTests.swift` (4 tests, 84 loc) + +### macOS/LocalPackages/NetworkProtectionMac (1 files) +- `macOS/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionProxyTests/OrphanProxyBucketTests.swift` (2 tests, 48 loc) + +### SharedPackages/AttributedMetric/Tests (1 files) +- `SharedPackages/AttributedMetric/Tests/AttributedMetricTests/InstallOriginParserTests.swift` (6 tests, 72 loc) + +### iOS/LocalPackages/DataBrokerProtection-iOS (1 files) +- `iOS/LocalPackages/DataBrokerProtection-iOS/Tests/DataBrokerProtection-iOSTests/FreemiumDBPPurchaseURLRouterTests.swift` (6 tests, 89 loc) + +## Full list: zero-marker files + + +### SharedPackages/AIChat/Tests (34) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatModelLabelTests.swift` (1 tests, 29 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatClearingReportTests.swift` (3 tests, 45 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatPromptHandlerTests.swift` (3 tests, 53 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatModelTests.swift` (4 tests, 54 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/UsageWarnings/DuckAiUsageResetIntervalTests.swift` (5 tests, 59 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/PageContextBlocklistSettingsTests.swift` (5 tests, 63 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatPDFInspectorTests.swift` (4 tests, 72 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatConversationSourceHandlerTests.swift` (6 tests, 72 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatModelImageGenerationTests.swift` (3 tests, 78 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatContextChipViewTests.swift` (5 tests, 88 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatModelSectionBuilderTests.swift` (4 tests, 96 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatMigrationStoreTests.swift` (5 tests, 100 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/UsageWarnings/DuckAiUsageWarningMessagePreviewTests.swift` (10 tests, 115 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatHistorySettingsTests.swift` (7 tests, 121 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatAttachmentUsageTests.swift` (7 tests, 122 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/EditPromptMessagesTests.swift` (7 tests, 123 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/PageContextAttachabilityPolicyTests.swift` (19 tests, 130 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatPageContextDocumentTests.swift` (8 tests, 148 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatModelPublicAccessTierTests.swift` (16 tests, 157 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatTabPickerTypesTests.swift` (10 tests, 173 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/UsageWarnings/DuckAiModelSuggesterTests.swift` (13 tests, 181 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatNativeConfigValuesTests.swift` (18 tests, 182 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/ChatPinnerTests.swift` (10 tests, 190 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatURLParametersTests.swift` (23 tests, 228 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/PageContextExtractionResolverTests.swift` (22 tests, 241 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatSuggestionTests.swift` (28 tests, 275 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/CustomizeResponsesStateTests.swift` (29 tests, 286 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatReasoningTests.swift` (23 tests, 295 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatAttachmentValidatorTests.swift` (26 tests, 300 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/ChatExporterTests.swift` (12 tests, 323 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatPageContextDataTests.swift` (18 tests, 331 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/URLExtensionTests.swift` (41 tests, 357 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/DuckAiChatDecodeTests.swift` (19 tests, 372 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/NativeStorage/DuckAiUsageSnapshotTests.swift` (36 tests, 416 loc) + +### SharedPackages/AttributedMetric/Tests (4) +- `SharedPackages/AttributedMetric/Tests/AttributedMetricTests/InstallOriginParserTests.swift` (6 tests, 72 loc) +- `SharedPackages/AttributedMetric/Tests/AttributedMetricTests/InstallOriginVariantResolverTests.swift` (13 tests, 94 loc) +- `SharedPackages/AttributedMetric/Tests/AttributedMetricTests/QuantisedTimePastTests.swift` (5 tests, 169 loc) +- `SharedPackages/AttributedMetric/Tests/AttributedMetricTests/BucketModifierTests.swift` (32 tests, 446 loc) + +### SharedPackages/AutomationServer/Tests (1) +- `SharedPackages/AutomationServer/Tests/AutomationServerTests/JSONEncodingTests.swift` (14 tests, 128 loc) + +### SharedPackages/BrowserServicesKit/Tests (75) +- `SharedPackages/BrowserServicesKit/Tests/LinuxMain.swift` (0 tests, 25 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/XCTestManifests.swift` (0 tests, 27 loc) +- `SharedPackages/BrowserServicesKit/Tests/PrivacyDashboardTests/PageSignalsSettingsTests.swift` (2 tests, 32 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/ContentScopeScriptTests/ContentScopePropertiesTests.swift` (1 tests, 33 loc) +- `SharedPackages/BrowserServicesKit/Tests/DDGSyncTests/DeviceInfoUpdateBuilderTests.swift` (1 tests, 39 loc) +- `SharedPackages/BrowserServicesKit/Tests/DDGSyncTests/ScopedAccess/ScopedAccessKeyFactoryTests.swift` (2 tests, 41 loc) +- `SharedPackages/BrowserServicesKit/Tests/UserScriptTests/UserScriptEncrypterTests.swift` (1 tests, 44 loc) +- `SharedPackages/BrowserServicesKit/Tests/SubscriptionTests/Flows/Models/PurchaseUpdateTests.swift` (3 tests, 46 loc) +- `SharedPackages/BrowserServicesKit/Tests/UserScriptTests/JSFileCacheTests.swift` (3 tests, 46 loc) +- `SharedPackages/BrowserServicesKit/Tests/ConfigurationTests/ConfigurationFileReadErrorTests.swift` (4 tests, 47 loc) +- `SharedPackages/BrowserServicesKit/Tests/SubscriptionTests/API/Models/SubscriptionEntitlementTests.swift` (2 tests, 48 loc) +- `SharedPackages/BrowserServicesKit/Tests/SecureStorageTests/SecureVaultFactoryTests.swift` (1 tests, 51 loc) +- `SharedPackages/BrowserServicesKit/Tests/PrivacyDashboardTests/ExpiryStorageTests.swift` (2 tests, 51 loc) +- `SharedPackages/BrowserServicesKit/Tests/SuggestionsTests/SuggestionResultTests.swift` (3 tests, 51 loc) +- `SharedPackages/BrowserServicesKit/Tests/PrivacyConfigTests/InternalUserDecider/DefaultInternalUserDeciderTests.swift` (4 tests, 52 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/ContentBlocker/DetectedRequestTests.swift` (2 tests, 54 loc) +- `SharedPackages/BrowserServicesKit/Tests/PrivacyDashboardTests/BrokenSiteReportHistoryEntryTests.swift` (3 tests, 54 loc) +- `SharedPackages/BrowserServicesKit/Tests/RemoteMessagingTests/Model/RangeStringMatchingAttributeTests.swift` (3 tests, 55 loc) +- `SharedPackages/BrowserServicesKit/Tests/MaliciousSiteProtectionTests/MaliciousSiteProtectionURLTests.swift` (3 tests, 58 loc) +- `SharedPackages/BrowserServicesKit/Tests/PrivacyDashboardTests/PrivacyInfoTests.swift` (3 tests, 62 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/ContentBlocker/DomainMatchingReportTests.swift` (1 tests, 63 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/SmarterEncryption/HTTPSUpgradeParserTests.swift` (6 tests, 68 loc) +- `SharedPackages/BrowserServicesKit/Tests/MaliciousSiteProtectionTests/MaliciousSiteProtectionEmbeddedDataProviderTest.swift` (1 tests, 70 loc) +- `SharedPackages/BrowserServicesKit/Tests/RemoteMessagingTests/Model/ArrayContainsAllMatchingAttributeTests.swift` (9 tests, 70 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/Statistics/AtbTests.swift` (5 tests, 72 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/SmarterEncryption/BloomFilterWrapperTest.swift` (3 tests, 74 loc) +- `SharedPackages/BrowserServicesKit/Tests/RemoteMessagingTests/Matchers/MobileAppAttributeMatcherTests.swift` (5 tests, 80 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/LinkProtection/AMPKeywordSearchTests.swift` (7 tests, 81 loc) +- `SharedPackages/BrowserServicesKit/Tests/DDGSyncTests/ScopedAccess/AccountInfoKeyFactoryTests.swift` (3 tests, 87 loc) +- `SharedPackages/BrowserServicesKit/Tests/SuggestionsTests/APIResultTests.swift` (3 tests, 87 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/ContentBlocker/DomainMatchingTests.swift` (1 tests, 93 loc) +- `SharedPackages/BrowserServicesKit/Tests/DDGSyncTests/ScopedAccess/ScopedAccessCredentialEnvelopeTests.swift` (4 tests, 93 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/SecureVault/CryptoProviderTests.swift` (5 tests, 93 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/Autofill/AutofillInterfaceEmailTruncatorTests.swift` (8 tests, 95 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/Watchdog/WatchdogDetectionStateTests.swift` (5 tests, 99 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/ContentBlocker/ContentBlockingDatasetContractTests.swift` (2 tests, 100 loc) +- `SharedPackages/BrowserServicesKit/Tests/DDGSyncTests/DeviceInfoCodecTests.swift` (4 tests, 100 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/ContentScopeScript/ContentScopePrivacyConfigurationJSONGeneratorTests.swift` (3 tests, 101 loc) +- `SharedPackages/BrowserServicesKit/Tests/PrivacyConfigTests/AdClickAttributionFeatureTests.swift` (1 tests, 107 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/ReferrerTrimming/ReferrerTrimmingTests.swift` (1 tests, 113 loc) +- `SharedPackages/BrowserServicesKit/Tests/RemoteMessagingTests/Matchers/DeviceAttributeMatcherTests.swift` (13 tests, 113 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/PrivacyConfig/PrivacyConfigurationReferenceTests.swift` (1 tests, 114 loc) +- `SharedPackages/BrowserServicesKit/Tests/PrivacyDashboardTests/SiteLoadingPerformanceSubfeatureTests.swift` (5 tests, 119 loc) +- `SharedPackages/BrowserServicesKit/Tests/DDGSyncCryptoTests/DDGSyncCryptoTests.swift` (4 tests, 121 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/ContentScopeScript/TrackerProtectionSubfeatureTests.swift` (9 tests, 126 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/SecureVault/CreditCardValidationTests.swift` (5 tests, 135 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/Autofill/AutofillUserScriptSourceProviderTests.swift` (4 tests, 143 loc) +- `SharedPackages/BrowserServicesKit/Tests/DDGSyncTests/CrypterTests.swift` (6 tests, 143 loc) +- `SharedPackages/BrowserServicesKit/Tests/DDGSyncTests/PairingInfoTests.swift` (11 tests, 143 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/ContentBlocker/TrackerDataManagerTests.swift` (5 tests, 147 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/ContentBlocker/AdClickAttribution/AdClickAttributionRulesSplitterTests.swift` (4 tests, 148 loc) +- `SharedPackages/BrowserServicesKit/Tests/SuggestionsTests/ScoreTests.swift` (10 tests, 149 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/DataImport/WebsiteAccount_isDuplicateTests.swift` (17 tests, 150 loc) +- `SharedPackages/BrowserServicesKit/Tests/DDGSyncTests/JWECompactCodecTests.swift` (7 tests, 168 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/Autofill/Matchers/AutofillDomainNameUrlMatcherTests.swift` (21 tests, 172 loc) +- `SharedPackages/BrowserServicesKit/Tests/SuggestionsTests/SuggestionTests.swift` (9 tests, 180 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/LinkProtection/AmpMatchingTests.swift` (4 tests, 181 loc) +- `SharedPackages/BrowserServicesKit/Tests/DuckPlayerTests/DuckPlayerURLExtensionTests.swift` (12 tests, 190 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/LinkProtection/URLParameterTests.swift` (6 tests, 202 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/ContentBlocker/TrackerDataQueryExtensionTests.swift` (3 tests, 220 loc) +- `SharedPackages/BrowserServicesKit/Tests/PrivacyConfigTests/PrivacyConfigurationDataTests.swift` (4 tests, 226 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/DataImport/CSVParserTests.swift` (18 tests, 226 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/Autofill/Sort/AutofillDomainNameUrlSortTests.swift` (22 tests, 258 loc) +- `SharedPackages/BrowserServicesKit/Tests/RemoteMessagingTests/RemoteMessagingConfigProcessorTests.swift` (8 tests, 278 loc) +- `SharedPackages/BrowserServicesKit/Tests/SubscriptionTests/FreeTrialConversionWideEventDataTests.swift` (23 tests, 295 loc) +- `SharedPackages/BrowserServicesKit/Tests/RemoteMessagingTests/Mappers/JsonToRemoteConfigModelMapperTests.swift` (6 tests, 370 loc) +- `SharedPackages/BrowserServicesKit/Tests/DDGSyncTests/PairingV2/PairingV2MessageCryptoTests.swift` (29 tests, 411 loc) +- `SharedPackages/BrowserServicesKit/Tests/AutoconsentStatsTests/AutoconsentDailyUsagePackTests.swift` (28 tests, 522 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/ContentBlocker/TrackerResolverTests.swift` (18 tests, 683 loc) +- `SharedPackages/BrowserServicesKit/Tests/SubscriptionTests/SubscriptionURLTests.swift` (50 tests, 711 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/ContentScopeScript/TrackerProtectionEventMapperTests.swift` (37 tests, 715 loc) +- `SharedPackages/BrowserServicesKit/Tests/SubscriptionTests/API/Models/SubscriptionTests.swift` (36 tests, 803 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/SecureVault/SecureVaultModelTests.swift` (35 tests, 836 loc) +- `SharedPackages/BrowserServicesKit/Tests/DDGSyncTests/PairingV2/PairingV2StateMachineTests.swift` (60 tests, 970 loc) +- `SharedPackages/BrowserServicesKit/Tests/RemoteMessagingTests/Mappers/JsonToRemoteMessageModelMapperTests.swift` (30 tests, 973 loc) + +### SharedPackages/Common/Tests (6) +- `SharedPackages/Common/Tests/CommonTests/DecodableHelperTests.swift` (3 tests, 44 loc) +- `SharedPackages/Common/Tests/CommonTests/Extensions/StringExtensionTests.swift` (4 tests, 45 loc) +- `SharedPackages/Common/Tests/CommonTests/DevicePlatformTests.swift` (4 tests, 58 loc) +- `SharedPackages/Common/Tests/CommonTests/Extensions/URLComponentsExtensionTests.swift` (8 tests, 130 loc) +- `SharedPackages/Common/Tests/CommonTests/TLD/TLDTests.swift` (28 tests, 168 loc) +- `SharedPackages/Common/Tests/CommonTests/ClosureSamplerTests.swift` (15 tests, 219 loc) + +### SharedPackages/DDGError/Tests (1) +- `SharedPackages/DDGError/Tests/DDGErrorTests/DDGErrorTests.swift` (15 tests, 228 loc) + +### SharedPackages/DataBrokerProtectionCore/Tests (19) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/BrokerJSONServiceProviderTests.swift` (3 tests, 37 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/DataBrokerExecutionConfigTests.swift` (4 tests, 51 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/ProfileQueryTests.swift` (4 tests, 53 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/BackgroundTaskEventTests.swift` (3 tests, 57 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/StatsPixelsTriggerTests.swift` (3 tests, 63 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/ServicesAuthHeaderBuilderTests.swift` (8 tests, 73 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/DataBrokerProtectionErrorsTests.swift` (8 tests, 84 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/MapperToModelTests+OptOutEmailConfirmationDB.swift` (2 tests, 101 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/BrokerProfileJobQueueModeTests.swift` (8 tests, 112 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/BrokerProfileQueryDataTests.swift` (8 tests, 113 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/DataBrokerScanRequiresSubscriptionTests.swift` (11 tests, 134 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/DataBrokerProtectionCustomStatsProviderTests.swift` (7 tests, 135 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/StalledOperationCalculatorTests.swift` (9 tests, 265 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/ActionsHandlerTests.swift` (19 tests, 289 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/ExtractedProfileTests.swift` (22 tests, 371 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/ActionRequestEncodingTests.swift` (18 tests, 425 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/BrokerJSONCodableTests.swift` (3 tests, 426 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/DBPUICommunicationViewModelScanStateExtensionsTests.swift` (32 tests, 611 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/OperationPreferredDateCalculatorTests.swift` (41 tests, 907 loc) + +### SharedPackages/DebugServer/Tests (3) +- `SharedPackages/DebugServer/Tests/DebugServerTests/HTTPResponseTests.swift` (6 tests, 69 loc) +- `SharedPackages/DebugServer/Tests/DebugServerTests/ResponseSerializerTests.swift` (9 tests, 106 loc) +- `SharedPackages/DebugServer/Tests/DebugServerTests/RequestParserTests.swift` (15 tests, 148 loc) + +### SharedPackages/HangMetrics/Tests (2) +- `SharedPackages/HangMetrics/Tests/HangMetricsTests/HangMetricsSubscriberTests.swift` (4 tests, 103 loc) +- `SharedPackages/HangMetrics/Tests/HangMetricsTests/HangMetricsProcessorTests.swift` (11 tests, 110 loc) + +### SharedPackages/Infrastructure/DesignResourcesKit (1) +- `SharedPackages/Infrastructure/DesignResourcesKit/Tests/DesignResourcesKitTests/DesignResourcesKitTests.swift` (1 tests, 29 loc) + +### SharedPackages/Infrastructure/SystemFrameworksExtensions (6) +- `SharedPackages/Infrastructure/SystemFrameworksExtensions/Tests/FoundationExtensionsTests/BundleExtensionTests.swift` (1 tests, 27 loc) +- `SharedPackages/Infrastructure/SystemFrameworksExtensions/Tests/FoundationExtensionsTests/LocaleExtensionsTests.swift` (3 tests, 43 loc) +- `SharedPackages/Infrastructure/SystemFrameworksExtensions/Tests/FoundationExtensionsTests/TimeIntervalConvenienceTests.swift` (6 tests, 48 loc) +- `SharedPackages/Infrastructure/SystemFrameworksExtensions/Tests/FoundationExtensionsTests/SequenceExtensionTests.swift` (6 tests, 56 loc) +- `SharedPackages/Infrastructure/SystemFrameworksExtensions/Tests/FoundationExtensionsTests/DictionaryExtensionTests.swift` (6 tests, 105 loc) +- `SharedPackages/Infrastructure/SystemFrameworksExtensions/Tests/FoundationExtensionsTests/CalendarExtensionTests.swift` (7 tests, 127 loc) + +### SharedPackages/Networking/Tests (6) +- `SharedPackages/Networking/Tests/NetworkingTests/v2/Extensions/HTTPURLResponseETagTests.swift` (5 tests, 67 loc) +- `SharedPackages/Networking/Tests/NetworkingTests/v2/Extensions/HTTPURLResponseCookiesTests.swift` (4 tests, 84 loc) +- `SharedPackages/Networking/Tests/NetworkingTests/v2/Extensions/URL+QueryParametersTests.swift` (6 tests, 95 loc) +- `SharedPackages/Networking/Tests/NetworkingTests/v2/Extensions/DictionaryURLQueryItemsTests.swift` (6 tests, 117 loc) +- `SharedPackages/Networking/Tests/NetworkingTests/Auth/TokenContainerTests.swift` (9 tests, 138 loc) +- `SharedPackages/Networking/Tests/NetworkingTests/v2/APIRequestV2Tests.swift` (5 tests, 143 loc) + +### SharedPackages/Onboarding/Sources (2) +- `SharedPackages/Onboarding/Sources/OnboardingTests/OnboardingSuggestedSitesProviderTests.swift` (10 tests, 175 loc) +- `SharedPackages/Onboarding/Sources/OnboardingTests/OnboardingSharedPixelTests.swift` (36 tests, 570 loc) + +### SharedPackages/PixelKit/Tests (1) +- `SharedPackages/PixelKit/Tests/PixelKitTests/RetryQueue/PixelRetryQueueItemTests.swift` (3 tests, 62 loc) + +### SharedPackages/VPN/Tests (23) +- `SharedPackages/VPN/Tests/VPNTests/ExponentialGeometricAverageTests.swift` (1 tests, 31 loc) +- `SharedPackages/VPN/Tests/VPNTests/KnownFailureTests.swift` (2 tests, 36 loc) +- `SharedPackages/VPN/Tests/VPNTests/ConnectionAttemptSourceTests.swift` (2 tests, 42 loc) +- `SharedPackages/VPN/Tests/VPNTests/Notifications/DistributedNotificationObjectCodersTests.swift` (1 tests, 45 loc) +- `SharedPackages/VPN/Tests/VPNTests/NetworkProtectionDataVolumeBucketsTests.swift` (4 tests, 51 loc) +- `SharedPackages/VPN/Tests/VPNTests/LeakCheck/LeakCheckHTTPClientTests.swift` (5 tests, 54 loc) +- `SharedPackages/VPN/Tests/VPNTests/TunnelConfigurationEndpointPortTests.swift` (2 tests, 55 loc) +- `SharedPackages/VPN/Tests/VPNTests/NetworkProtectionIPAddressCategoryTests.swift` (4 tests, 55 loc) +- `SharedPackages/VPN/Tests/VPNTests/PacketTunnelProviderTests.swift` (4 tests, 64 loc) +- `SharedPackages/VPN/Tests/VPNTests/NetworkProtectionConnectionBandwidthAnalyzerTests.swift` (2 tests, 65 loc) +- `SharedPackages/VPN/Tests/VPNTests/EndpointTests.swift` (5 tests, 72 loc) +- `SharedPackages/VPN/Tests/VPNTests/LeakCheck/IPAddressClassifierTests.swift` (12 tests, 80 loc) +- `SharedPackages/VPN/Tests/VPNTests/LeakCheck/STUNMessageTests.swift` (6 tests, 92 loc) +- `SharedPackages/VPN/Tests/VPNTests/NetworkProtectionServerInfoEndpointPortCandidatesTests.swift` (10 tests, 96 loc) +- `SharedPackages/VPN/Tests/VPNTests/NetworkProtectionServerInfoTests.swift` (4 tests, 108 loc) +- `SharedPackages/VPN/Tests/VPNTests/Status/VPNEnabledObserverTests.swift` (10 tests, 117 loc) +- `SharedPackages/VPN/Tests/VPNTests/LeakCheck/VPNIPLeakCheckWideEventDataTests.swift` (8 tests, 146 loc) +- `SharedPackages/VPN/Tests/VPNTests/Routing/VPNRoutingMathematicsTests.swift` (7 tests, 189 loc) +- `SharedPackages/VPN/Tests/VPNTests/Subscription/VPNSubscriptionClientCheckPixelTests.swift` (15 tests, 211 loc) +- `SharedPackages/VPN/Tests/VPNTests/Subscription/VPNSubscriptionStatusPixelTests.swift` (19 tests, 295 loc) +- `SharedPackages/VPN/Tests/VPNTests/Routing/VPNRoutingRangeTests.swift` (12 tests, 305 loc) +- `SharedPackages/VPN/Tests/VPNTests/Routing/VPNRoutingTableResolverTests.swift` (14 tests, 320 loc) +- `SharedPackages/VPN/Tests/VPNTests/Routing/VPNRoutingIntegrationTests.swift` (12 tests, 527 loc) + +### SharedPackages/WebExtensions/Tests (6) +- `SharedPackages/WebExtensions/Tests/WebExtensionsTests/WebExtensionPixelExtensionTests.swift` (2 tests, 39 loc) +- `SharedPackages/WebExtensions/Tests/WebExtensionsTests/NativeMessagingFramingTests.swift` (5 tests, 68 loc) +- `SharedPackages/WebExtensions/Tests/WebExtensionsTests/WebExtensionAPICompatibilityLogTests.swift` (6 tests, 89 loc) +- `SharedPackages/WebExtensions/Tests/WebExtensionsTests/Scriptlets/ScriptletPathSafetyTests.swift` (13 tests, 98 loc) +- `SharedPackages/WebExtensions/Tests/WebExtensionsTests/WebExtensionAPICompatibilityClassifierTests.swift` (20 tests, 187 loc) +- `SharedPackages/WebExtensions/Tests/WebExtensionsTests/ChromeWebStorePackageVerifierTests.swift` (17 tests, 230 loc) + +### iOS/DuckDuckGoTests (79) +- `iOS/DuckDuckGoTests/AIChat/AIChatContextualSheetViewControllerTests.swift` (1 tests, 32 loc) +- `iOS/DuckDuckGoTests/SERPSettingsViewPageTests.swift` (1 tests, 32 loc) +- `iOS/DuckDuckGoTests/ImproveOnboardingExperiment1Tests.swift` (1 tests, 35 loc) +- `iOS/DuckDuckGoTests/BrowsingMenu/BrowsingMenuSheetCapabilityTests.swift` (2 tests, 39 loc) +- `iOS/DuckDuckGoTests/FaviconSourcesProviderTests.swift` (2 tests, 39 loc) +- `iOS/DuckDuckGoTests/UnifiedToggleInput/UnifiedInputTextChangeGateTests.swift` (3 tests, 40 loc) +- `iOS/DuckDuckGoTests/UnifiedToggleInput/UpdatedModelPickerFeatureTests.swift` (2 tests, 41 loc) +- `iOS/DuckDuckGoTests/UserScriptErrorTests.swift` (1 tests, 42 loc) +- `iOS/DuckDuckGoTests/UIFontExtensionTests.swift` (4 tests, 43 loc) +- `iOS/DuckDuckGoTests/UnifiedDeviceListPixelHandlerTests.swift` (1 tests, 44 loc) +- `iOS/DuckDuckGoTests/LaunchTabNotificationTests.swift` (2 tests, 45 loc) +- `iOS/DuckDuckGoTests/HomePageDisplayDailyPixelBucketTests.swift` (1 tests, 48 loc) +- `iOS/DuckDuckGoTests/UnifiedToggleInput/TabInputStateTests.swift` (3 tests, 48 loc) +- `iOS/DuckDuckGoTests/APIHeadersTests.swift` (2 tests, 51 loc) +- `iOS/DuckDuckGoTests/AppPrivacyConfigurationTests.swift` (2 tests, 52 loc) +- `iOS/DuckDuckGoTests/AIChat/DuckAIVoiceControlWidgetTests.swift` (3 tests, 52 loc) +- `iOS/DuckDuckGoTests/UnifiedToggleInput/AITabChromeDecisionTests.swift` (2 tests, 53 loc) +- `iOS/DuckDuckGoTests/AppWidthObserverTests.swift` (4 tests, 53 loc) +- `iOS/DuckDuckGoTests/AIChat/AIChatTextSelectionActionTests.swift` (6 tests, 54 loc) +- `iOS/DuckDuckGoTests/NetworkProtectionStatusViewTintSpecTests.swift` (1 tests, 56 loc) +- `iOS/DuckDuckGoTests/TabEvictionSettingsTests.swift` (2 tests, 56 loc) +- `iOS/DuckDuckGoTests/UnifiedToggleInput/MultiTabMentionTokenTests.swift` (3 tests, 56 loc) +- `iOS/DuckDuckGoTests/AppServices/InactivityNotificationStateStoreTests.swift` (4 tests, 56 loc) +- `iOS/DuckDuckGoTests/BookmarkOrFolderTests.swift` (5 tests, 56 loc) +- `iOS/DuckDuckGoTests/AIChat/AIVoiceChatIntentTests.swift` (5 tests, 58 loc) +- `iOS/DuckDuckGoTests/TabViewControllerSiteLoadingPixelTests.swift` (4 tests, 61 loc) +- `iOS/DuckDuckGoTests/BackForwardMenuHistoryItemURLSanitizerTests.swift` (5 tests, 61 loc) +- `iOS/DuckDuckGoTests/Autocomplete/SuggestionListKeyboardSelectionTests.swift` (9 tests, 61 loc) +- `iOS/DuckDuckGoTests/AutofillInterfaceUsernameTruncatorTests.swift` (5 tests, 64 loc) +- `iOS/DuckDuckGoTests/SearchToken/SearchTokenExperimentSettingsTests.swift` (5 tests, 65 loc) +- `iOS/DuckDuckGoTests/DomainMatchingReportTests.swift` (1 tests, 66 loc) +- `iOS/DuckDuckGoTests/AtbParserTests.swift` (6 tests, 66 loc) +- `iOS/DuckDuckGoTests/SettingsDeepLinkSectionTests.swift` (6 tests, 66 loc) +- `iOS/DuckDuckGoTests/ExternalUrlSchemeTests.swift` (7 tests, 66 loc) +- `iOS/DuckDuckGoTests/UnifiedToggleInput/MultiTabAttachmentCandidateFilterTests.swift` (4 tests, 69 loc) +- `iOS/DuckDuckGoTests/Subscription/URL+QueryParametersTests.swift` (9 tests, 70 loc) +- `iOS/DuckDuckGoTests/LaunchTimeMetricsSubscriberTests.swift` (2 tests, 72 loc) +- `iOS/DuckDuckGoTests/UnifiedToggleInput/UnifiedToggleInputToggleViewTests.swift` (5 tests, 74 loc) +- `iOS/DuckDuckGoTests/DataImport/ImportPasswordSourceTests.swift` (7 tests, 74 loc) +- `iOS/DuckDuckGoTests/Subscription/AsyncHeadlessWebViewSettingsTests.swift` (3 tests, 75 loc) +- `iOS/DuckDuckGoTests/HomeScreenTransitionGeometryTests.swift` (4 tests, 75 loc) +- `iOS/DuckDuckGoTests/UnifiedToggleInput/ToolbarVisibilityDecisionTests.swift` (5 tests, 75 loc) +- `iOS/DuckDuckGoTests/Subscription/SubscriptionExperimentAttributionProviderTests.swift` (3 tests, 78 loc) +- `iOS/DuckDuckGoTests/OmniBarLoadingStateBearerTests.swift` (3 tests, 79 loc) +- `iOS/DuckDuckGoTests/AIChat/AIChatContextualQuickActionTests.swift` (11 tests, 80 loc) +- `iOS/DuckDuckGoTests/UnifiedToggleInput/Suggestions/SuggestionRowMapperTests.swift` (7 tests, 87 loc) +- `iOS/DuckDuckGoTests/SupportedOSCheckerTests.swift` (9 tests, 91 loc) +- `iOS/DuckDuckGoTests/ToggleMode/DefaultTogglePositionTests.swift` (9 tests, 93 loc) +- `iOS/DuckDuckGoTests/UnifiedToggleInput/Suggestions/UnifiedSuggestionsContentResolverTests.swift` (9 tests, 93 loc) +- `iOS/DuckDuckGoTests/DomainMatchingTests.swift` (1 tests, 95 loc) +- `iOS/DuckDuckGoTests/LaunchTimeMetricsProcessorTests.swift` (9 tests, 96 loc) +- `iOS/DuckDuckGoTests/DaxDialogsBrowsingSpecTests.swift` (6 tests, 102 loc) +- `iOS/DuckDuckGoTests/UnifiedToggleInput/Suggestions/FocusedLogoModelTests.swift` (9 tests, 102 loc) +- `iOS/DuckDuckGoTests/SearchToken/SerpSearchTokenInterceptorTests.swift` (13 tests, 109 loc) +- `iOS/DuckDuckGoTests/LinkTests.swift` (13 tests, 117 loc) +- `iOS/DuckDuckGoTests/TabSwitcherStaticButtonTests.swift` (10 tests, 119 loc) +- `iOS/DuckDuckGoTests/UnifiedToggleInput/Suggestions/UnifiedSuggestionsInputsMergerTests.swift` (9 tests, 123 loc) +- `iOS/DuckDuckGoTests/AddressDisplayHelperTests.swift` (10 tests, 133 loc) +- `iOS/DuckDuckGoTests/OmniBarEqualityCheckTests.swift` (6 tests, 147 loc) +- `iOS/DuckDuckGoTests/IPadOmnibarFocusModelTests.swift` (19 tests, 148 loc) +- `iOS/DuckDuckGoTests/AIChat/AIBoundaryNavigationDecisionTests.swift` (12 tests, 160 loc) +- `iOS/DuckDuckGoTests/TabViewControllerHTTPSForcedTests.swift` (13 tests, 160 loc) +- `iOS/DuckDuckGoTests/UnifiedToggleInput/UnifiedToggleInputAttachmentEncoderTests.swift` (16 tests, 161 loc) +- `iOS/DuckDuckGoTests/WebViewTransitionGeometryTests.swift` (20 tests, 166 loc) +- `iOS/DuckDuckGoTests/AIChat/DuckAIAddressBarEntryTests.swift` (19 tests, 169 loc) +- `iOS/DuckDuckGoTests/URLExtensionTests.swift` (27 tests, 177 loc) +- `iOS/DuckDuckGoTests/Subscription/SubscriptionFlowTypeTests.swift` (11 tests, 183 loc) +- `iOS/DuckDuckGoTests/AIChat/DuckAIGridItemTests.swift` (16 tests, 191 loc) +- `iOS/DuckDuckGoTests/UnifiedToggleInput/MainViewControllerRefreshActionTests.swift` (12 tests, 199 loc) +- `iOS/DuckDuckGoTests/SwitchBar/SwitchBarSubmissionMetricsTests.swift` (22 tests, 204 loc) +- `iOS/DuckDuckGoTests/NewBadge/NewBadgeVisibilityManagerTests.swift` (10 tests, 218 loc) +- `iOS/DuckDuckGoTests/StringExtensionTests.swift` (18 tests, 222 loc) +- `iOS/DuckDuckGoTests/TabSwitcherOpenDailyPixelTests.swift` (8 tests, 253 loc) +- `iOS/DuckDuckGoTests/AutofillLoginPromptViewModelTests.swift` (19 tests, 330 loc) +- `iOS/DuckDuckGoTests/UnifiedToggleInput/Footer/UTIFooterMessageMapperTests.swift` (32 tests, 338 loc) +- `iOS/DuckDuckGoTests/UnifiedToggleInput/UnifiedToggleInputModelMenuTests.swift` (20 tests, 340 loc) +- `iOS/DuckDuckGoTests/TabsModelTests.swift` (46 tests, 487 loc) +- `iOS/DuckDuckGoTests/DataImport/CredentialExchangeImportHandlerTests.swift` (13 tests, 555 loc) +- `iOS/DuckDuckGoTests/UnifiedToggleInput/UTIAttachmentPolicyTests.swift` (61 tests, 655 loc) + +### iOS/LocalPackages/AppRouting (2) +- `iOS/LocalPackages/AppRouting/Tests/AppRoutingTests/AppDeepLinksTests.swift` (16 tests, 97 loc) +- `iOS/LocalPackages/AppRouting/Tests/AppRoutingTests/URLInputClassifierTests.swift` (18 tests, 140 loc) + +### iOS/LocalPackages/DataBrokerProtection-iOS (3) +- `iOS/LocalPackages/DataBrokerProtection-iOS/Tests/DataBrokerProtection-iOSTests/FreemiumDBPPurchaseURLRouterTests.swift` (6 tests, 89 loc) +- `iOS/LocalPackages/DataBrokerProtection-iOS/Tests/DataBrokerProtection-iOSTests/DBPContinuedProcessingPlanBuilderTests.swift` (3 tests, 133 loc) +- `iOS/LocalPackages/DataBrokerProtection-iOS/Tests/DataBrokerProtection-iOSTests/DBPContinuedProcessingProgressReporterTests.swift` (6 tests, 153 loc) + +### iOS/LocalPackages/FeatureFlags-iOS (1) +- `iOS/LocalPackages/FeatureFlags-iOS/Tests/FeatureFlagsTests/FeatureFlagsTests.swift` (11 tests, 197 loc) + +### iOS/LocalPackages/SitePermissions (4) +- `iOS/LocalPackages/SitePermissions/Tests/SitePermissionsTests/SitePermissionSecurityOriginTests.swift` (3 tests, 74 loc) +- `iOS/LocalPackages/SitePermissions/Tests/SitePermissionsTests/SitePermissionsEventTests.swift` (4 tests, 83 loc) +- `iOS/LocalPackages/SitePermissions/Tests/SitePermissionsTests/SitePermissionKeyTests.swift` (9 tests, 91 loc) +- `iOS/LocalPackages/SitePermissions/Tests/SitePermissionsTests/PermissionsPolicyTests.swift` (11 tests, 140 loc) + +### iOS/WebViewUnitTests (2) +- `iOS/WebViewUnitTests/CookieStorageTests.swift` (1 tests, 81 loc) +- `iOS/WebViewUnitTests/DataStoreIDManagerTests.swift` (7 tests, 93 loc) + +### macOS/LocalPackages/AddressBarPerformance (2) +- `macOS/LocalPackages/AddressBarPerformance/Tests/AddressBarPerformanceTests/AddressBarPerformancePaintHookTests.swift` (3 tests, 48 loc) +- `macOS/LocalPackages/AddressBarPerformance/Tests/AddressBarPerformanceTests/AddressBarPerformanceBucketingTests.swift` (12 tests, 126 loc) + +### macOS/LocalPackages/AppKitExtensions (2) +- `macOS/LocalPackages/AppKitExtensions/Tests/AppKitExtensionsTests/NSMenuItemExtensionTests.swift` (2 tests, 56 loc) +- `macOS/LocalPackages/AppKitExtensions/Tests/AppKitExtensionsTests/AverageBrightnessTests.swift` (1 tests, 66 loc) + +### macOS/LocalPackages/AppLauncher (1) +- `macOS/LocalPackages/AppLauncher/Tests/AppLauncherTests/AppLauncherTests.swift` (0 tests, 24 loc) + +### macOS/LocalPackages/AppUpdater (4) +- `macOS/LocalPackages/AppUpdater/Tests/SparkleAppUpdaterTests/SparkleUpdateControllerTests.swift` (4 tests, 63 loc) +- `macOS/LocalPackages/AppUpdater/Tests/AppUpdaterSharedTests/ReleaseNotesParserTests.swift` (10 tests, 176 loc) +- `macOS/LocalPackages/AppUpdater/Tests/AppUpdaterSharedTests/UpdateModelTests.swift` (8 tests, 189 loc) +- `macOS/LocalPackages/AppUpdater/Tests/SparkleAppUpdaterTests/UpdateWideEventDataTests.swift` (15 tests, 446 loc) + +### macOS/LocalPackages/BWIntegration (6) +- `macOS/LocalPackages/BWIntegration/Tests/BWManagementTests/RunningApplicationCheckTests.swift` (2 tests, 39 loc) +- `macOS/LocalPackages/BWIntegration/Tests/BWManagementTests/BWRetryIntervalTests.swift` (2 tests, 45 loc) +- `macOS/LocalPackages/BWIntegration/Tests/BWManagementSharedTests/BWRequestTests.swift` (2 tests, 46 loc) +- `macOS/LocalPackages/BWIntegration/Tests/BWManagementSharedTests/BWMessageIdGeneratorTests.swift` (3 tests, 48 loc) +- `macOS/LocalPackages/BWIntegration/Tests/BWManagementTests/NativeMessagingCommunicatorTests.swift` (3 tests, 75 loc) +- `macOS/LocalPackages/BWIntegration/Tests/BWManagementSharedTests/BWResponseTests.swift` (8 tests, 144 loc) + +### macOS/LocalPackages/DataBrokerProtection-macOS (5) +- `macOS/LocalPackages/DataBrokerProtection-macOS/Tests/DataBrokerProtection-macOSTests/MemoryUsageMonitorTests.swift` (1 tests, 51 loc) +- `macOS/LocalPackages/DataBrokerProtection-macOS/Tests/DataBrokerProtection-macOSTests/CPUUsageMonitorTests.swift` (1 tests, 58 loc) +- `macOS/LocalPackages/DataBrokerProtection-macOS/Tests/DataBrokerProtection-macOSTests/ResourceUsagePixelReporterTests.swift` (2 tests, 116 loc) +- `macOS/LocalPackages/DataBrokerProtection-macOS/Tests/DataBrokerProtection-macOSTests/DataBrokerProtectionInMemoryCacheTests.swift` (9 tests, 117 loc) +- `macOS/LocalPackages/DataBrokerProtection-macOS/Tests/DataBrokerProtection-macOSTests/DBPUICommunicationModelTests.swift` (14 tests, 420 loc) + +### macOS/LocalPackages/HistoryView (2) +- `macOS/LocalPackages/HistoryView/Tests/HistoryViewTests/VisitIdentifierTests.swift` (2 tests, 40 loc) +- `macOS/LocalPackages/HistoryView/Tests/HistoryViewTests/HistoryRangeExtensionTests.swift` (9 tests, 101 loc) + +### macOS/LocalPackages/NetworkProtectionMac (7) +- `macOS/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionUITests/OnboardingStatusTests.swift` (1 tests, 42 loc) +- `macOS/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionProxyTests/OrphanProxyBucketTests.swift` (2 tests, 48 loc) +- `macOS/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionProxyTests/TransparentProxyProviderExclusionTests.swift` (7 tests, 57 loc) +- `macOS/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionProxyTests/TransparentProxyProviderPixelTests.swift` (1 tests, 65 loc) +- `macOS/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionUITests/NetworkProtectionAssetTests.swift` (1 tests, 67 loc) +- `macOS/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionProxyTests/OrphanProxyDecisionTests.swift` (8 tests, 116 loc) +- `macOS/LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionProxyTests/TransparentProxyControllerPixelTests.swift` (2 tests, 126 loc) + +### macOS/LocalPackages/NetworkQualityMonitor (1) +- `macOS/LocalPackages/NetworkQualityMonitor/Tests/NetworkQualityMonitorTests/NetworkQualityMonitorTests.swift` (16 tests, 285 loc) + +### macOS/LocalPackages/NewTabPage (2) +- `macOS/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPageUserScriptTests.swift` (2 tests, 69 loc) +- `macOS/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPageDataModelSuggestionTests.swift` (7 tests, 95 loc) + +### macOS/LocalPackages/PerformanceTest (2) +- `macOS/LocalPackages/PerformanceTest/Tests/PerformanceTestTests/CoreWebVitalsAssessmentTests.swift` (5 tests, 54 loc) +- `macOS/LocalPackages/PerformanceTest/Tests/PerformanceTestTests/DetailedPerformanceMetricsTests.swift` (8 tests, 211 loc) + +### macOS/LocalPackages/UDSHelper (1) +- `macOS/LocalPackages/UDSHelper/Tests/UDSHelperTests/UDSMessageTests.swift` (1 tests, 46 loc) + +### macOS/UnitTests (83) +- `macOS/UnitTests/NetworkProtection/VPNUninstallerTests.swift` (1 tests, 31 loc) +- `macOS/UnitTests/UserAgent/Services/SafariVersionReaderTests.swift` (1 tests, 31 loc) +- `macOS/UnitTests/UserAgent/Services/WebKitVersionProviderTests.swift` (1 tests, 31 loc) +- `macOS/UnitTests/Common/Extensions/BundleExtensionTests.swift` (1 tests, 38 loc) +- `macOS/UnitTests/HomePage/ColorSchemeLosslessStringConvertibleExtensionTests.swift` (2 tests, 38 loc) +- `macOS/UnitTests/Common/Utilities/UserScriptErrorTests.swift` (1 tests, 40 loc) +- `macOS/UnitTests/Common/FileSystem/EncryptionKeyGeneratorTests.swift` (2 tests, 40 loc) +- `macOS/UnitTests/DataImport/ChromiumFaviconsReaderTests.swift` (1 tests, 42 loc) +- `macOS/UnitTests/DataImport/FirefoxFaviconsReaderTests.swift` (1 tests, 42 loc) +- `macOS/UnitTests/DataImport/ChromiumBookmarksReaderTests.swift` (1 tests, 43 loc) +- `macOS/UnitTests/DataImport/SafariBookmarksReaderTests.swift` (1 tests, 43 loc) +- `macOS/UnitTests/DefaultBrowserAndAddToDockPrompts/DefaultBrowserAndDockPromptNotificationPresenterTests.swift` (1 tests, 43 loc) +- `macOS/UnitTests/Sync/UnifiedDeviceListPixelHandlerTests.swift` (1 tests, 43 loc) +- `macOS/UnitTests/Common/Extensions/WKWebsiteDataStoreExtensionTests.swift` (2 tests, 43 loc) +- `macOS/UnitTests/Statistics/ATB/AtbParserTests.swift` (3 tests, 47 loc) +- `macOS/UnitTests/Permissions/PermissionTypeTests.swift` (4 tests, 47 loc) +- `macOS/UnitTests/TabExtensionsTests/SpecialErrorPageUserScriptTests.swift` (2 tests, 49 loc) +- `macOS/UnitTests/Subscription/URL+SubscriptionAttributionTests.swift` (3 tests, 50 loc) +- `macOS/UnitTests/ContentBlocker/AppPrivacyConfigurationTests.swift` (2 tests, 52 loc) +- `macOS/UnitTests/PasswordManagers/PasswordManagerCoordinatorTests.swift` (4 tests, 56 loc) +- `macOS/UnitTests/Common/Extensions/ArrayExtensionTests.swift` (7 tests, 58 loc) +- `macOS/UnitTests/Common/Utilities/ApplicationBuildTypeChannelTests.swift` (6 tests, 60 loc) +- `macOS/UnitTests/HomePage/UserBackgroundImageTests.swift` (3 tests, 62 loc) +- `macOS/UnitTests/AttributedMetric/DefaultInstallOriginVariantProviderTests.swift` (4 tests, 63 loc) +- `macOS/UnitTests/WebExtensions/WebExtensionPixelTests.swift` (4 tests, 63 loc) +- `macOS/UnitTests/Tab/Model/TabContentDisplayTitleTests.swift` (7 tests, 63 loc) +- `macOS/UnitTests/AIChat/DuckAIPromptSurfaceTests.swift` (5 tests, 64 loc) +- `macOS/UnitTests/Common/Extensions/HTTPCookieTests.swift` (1 tests, 66 loc) +- `macOS/UnitTests/TabBar/ViewModel/AdjacentItemEnumeratorTests.swift` (4 tests, 67 loc) +- `macOS/UnitTests/AIChat/AIChatTabSelectionDiffTests.swift` (5 tests, 71 loc) +- `macOS/UnitTests/Common/FileSystem/DataEncryptionTests.swift` (4 tests, 73 loc) +- `macOS/UnitTests/Feedback/QuickFeedback/AppVersionModelDistributionLabelTests.swift` (4 tests, 73 loc) +- `macOS/UnitTests/DataImport/FirefoxBookmarksReaderTests.swift` (2 tests, 75 loc) +- `macOS/UnitTests/Statistics/NewTabPagePixelTests.swift` (4 tests, 75 loc) +- `macOS/UnitTests/Promotions/PromoHistoryRecordTests.swift` (6 tests, 75 loc) +- `macOS/UnitTests/MainWindow/InitialWindowFrameProviderTests.swift` (4 tests, 78 loc) +- `macOS/UnitTests/AppHealth/StartupMetricsBucketsTests.swift` (4 tests, 80 loc) +- `macOS/UnitTests/NavigationBar/NavigationBarUrlExtensionsTests.swift` (1 tests, 86 loc) +- `macOS/UnitTests/HomePage/CustomBackgroundTests.swift` (6 tests, 89 loc) +- `macOS/UnitTests/PromptBar/PromptBarPlacementTests.swift` (8 tests, 89 loc) +- `macOS/UnitTests/Common/Extensions/StringExtensionTests.swift` (4 tests, 100 loc) +- `macOS/UnitTests/Bookmarks/Model/PasteboardBookmarkTests.swift` (6 tests, 100 loc) +- `macOS/UnitTests/Onboarding/ContextualOnboarding/OnboardingSubscriptionUpsellExperimentTests.swift` (5 tests, 101 loc) +- `macOS/UnitTests/Bookmarks/Model/PasteboardFolderTests.swift` (8 tests, 104 loc) +- `macOS/UnitTests/Onboarding/ContextualOnboarding/ViewHighlighterTests.swift` (5 tests, 105 loc) +- `macOS/UnitTests/Bookmarks/ViewModels/BookmarksSortModeTests.swift` (7 tests, 109 loc) +- `macOS/UnitTests/UserChurn/UserChurnPixelTests.swift` (10 tests, 110 loc) +- `macOS/UnitTests/Promotions/PromoTypeTests.swift` (4 tests, 112 loc) +- `macOS/UnitTests/TabBar/Tools/LoadingIndicatorPolicyTests.swift` (9 tests, 112 loc) +- `macOS/UnitTests/Common/FileSystem/EncryptedValueTransformerTests.swift` (4 tests, 113 loc) +- `macOS/UnitTests/UserAgent/Model/UserAgentTests.swift` (7 tests, 117 loc) +- `macOS/UnitTests/ErrorPage/ErrorPageHTMLFactoryTests.swift` (9 tests, 118 loc) +- `macOS/UnitTests/TabBar/Tools/TitleDisplayPolicyTests.swift` (13 tests, 121 loc) +- `macOS/UnitTests/History/View/HistoryQueryKindExtensionTests.swift` (6 tests, 125 loc) +- `macOS/UnitTests/AIChat/AIChatAddressBarPromptExtractorTests.swift` (8 tests, 127 loc) +- `macOS/UnitTests/Favicons/FaviconTests.swift` (10 tests, 129 loc) +- `macOS/UnitTests/AIChat/AIChatMentionTokenDetectorTests.swift` (20 tests, 144 loc) +- `macOS/UnitTests/AppHealth/StartupMetricsTests.swift` (13 tests, 145 loc) +- `macOS/UnitTests/DataImport/InstructionsFormatParserTests.swift` (10 tests, 146 loc) +- `macOS/UnitTests/Statistics/ATB/VariantManagerTests.swift` (10 tests, 154 loc) +- `macOS/UnitTests/AppHealth/TrackerProtectionMemoryGuardrailTests.swift` (3 tests, 161 loc) +- `macOS/UnitTests/SecureVault/PasswordManagementListSectionTests.swift` (8 tests, 161 loc) +- `macOS/UnitTests/AppHealth/MemoryReportingBucketsTests.swift` (13 tests, 162 loc) +- `macOS/UnitTests/AIChat/AIChatUsageWarningCopyTests.swift` (14 tests, 165 loc) +- `macOS/UnitTests/AIChat/AIChatMentionPickerFilterTests.swift` (12 tests, 171 loc) +- `macOS/UnitTests/DataExport/CSVLoginExporterTests.swift` (4 tests, 173 loc) +- `macOS/UnitTests/Tab/Model/TabContentCapabilityTests.swift` (12 tests, 175 loc) +- `macOS/UnitTests/PrivacyReferenceTests/BrokenSiteReportingReferenceTests.swift` (1 tests, 191 loc) +- `macOS/UnitTests/JSAlert/JSAlertViewModelTests.swift` (12 tests, 196 loc) +- `macOS/UnitTests/Permissions/PermissionAuthorizationTypeTests.swift` (32 tests, 200 loc) +- `macOS/UnitTests/DefaultBrowserAndAddToDockPrompts/DefaultBrowserAndDockPromptPixelEventTests.swift` (1 tests, 212 loc) +- `macOS/UnitTests/Feedback/Model/FeedbackTests.swift` (11 tests, 218 loc) +- `macOS/UnitTests/Common/OSVersion/SupportedOSCheckerTests.swift` (15 tests, 237 loc) +- `macOS/UnitTests/AppHealth/MemoryUsageIntervalPixelTests.swift` (10 tests, 241 loc) +- `macOS/UnitTests/AppHealth/StartupMetricsPixelTests.swift` (25 tests, 242 loc) +- `macOS/UnitTests/Suggestions/ViewModel/SuggestionViewModelTests.swift` (24 tests, 268 loc) +- `macOS/UnitTests/Model/FeedbackTests.swift` (15 tests, 271 loc) +- `macOS/UnitTests/Bookmarks/Model/BookmarkListTests.swift` (13 tests, 288 loc) +- `macOS/UnitTests/WebsiteBreakageReport/WebsiteBreakageReportTests.swift` (7 tests, 289 loc) +- `macOS/UnitTests/Bookmarks/Model/BaseBookmarkEntityTests.swift` (20 tests, 292 loc) +- `macOS/UnitTests/AIChat/BrowserTools/BrowserToolsWireFormatTests.swift` (20 tests, 301 loc) +- `macOS/UnitTests/Model/ProblemCategoryTests.swift` (22 tests, 302 loc) +- `macOS/UnitTests/DataImport/BrowserProfileTests.swift` (16 tests, 315 loc) + +## Full list: setUp-only files + + +### SharedPackages/AIChat/Tests (9) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatPayloadHandlerTests.swift` (3 tests, 64 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatPageContextHandlerTests.swift` (3 tests, 88 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatQuickActionsViewTests.swift` (5 tests, 101 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/UsageWarnings/DuckAIHighUsageModelNoticeResolverTests.swift` (10 tests, 109 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/ReasoningModeAccessResolverTests.swift` (6 tests, 110 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/NativeStorage/DuckAiNativeStorageBootstrapUserScriptTests.swift` (12 tests, 128 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatQuickActionChipViewTests.swift` (7 tests, 135 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/UsageWarnings/DuckAiUsageWarningResolverTests.swift` (21 tests, 268 loc) +- `SharedPackages/AIChat/Tests/AIChatTests/AIChatSuggestionsViewModelTests.swift` (26 tests, 445 loc) + +### SharedPackages/AttributedMetric/Tests (3) +- `SharedPackages/AttributedMetric/Tests/AttributedMetricTests/RollingArrayTests.swift` (10 tests, 287 loc) +- `SharedPackages/AttributedMetric/Tests/AttributedMetricTests/RollingEightDaysBoolTests.swift` (13 tests, 291 loc) +- `SharedPackages/AttributedMetric/Tests/AttributedMetricTests/RollingEightDaysIntTests.swift` (27 tests, 479 loc) + +### SharedPackages/BrowserServicesKit/Tests (25) +- `SharedPackages/BrowserServicesKit/Tests/SyncDataProvidersTests/Credentials/SyncableCredentialsValidationTests.swift` (6 tests, 77 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/GPC/GPCTests.swift` (6 tests, 80 loc) +- `SharedPackages/BrowserServicesKit/Tests/RemoteMessagingTests/Matchers/DesktopAppAttributeMatcherTests.swift` (5 tests, 92 loc) +- `SharedPackages/BrowserServicesKit/Tests/PixelExperimentKitTests/TDSOverrideExperimentMetricsTests.swift` (4 tests, 102 loc) +- `SharedPackages/BrowserServicesKit/Tests/ConfigurationTests/ConfigurationValidatorTests.swift` (5 tests, 109 loc) +- `SharedPackages/BrowserServicesKit/Tests/SubscriptionTests/FreeTrials/FreeTrialBadgePersistorTests.swift` (7 tests, 112 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/Autofill/Matchers/AutofillWebsiteAccountMatcherTests.swift` (8 tests, 115 loc) +- `SharedPackages/BrowserServicesKit/Tests/SyncDataProvidersTests/CreditCards/SyncableCreditCardsValidationTests.swift` (7 tests, 123 loc) +- `SharedPackages/BrowserServicesKit/Tests/SubscriptionTests/DuckDuckGoSubscriptionTests.swift` (8 tests, 130 loc) +- `SharedPackages/BrowserServicesKit/Tests/MaliciousSiteProtectionTests/MaliciousSiteProtectionFeatureFlagsTests.swift` (7 tests, 133 loc) +- `SharedPackages/BrowserServicesKit/Tests/PageRefreshMonitorTests/PageRefreshMonitorTests.swift` (11 tests, 145 loc) +- `SharedPackages/BrowserServicesKit/Tests/SyncDataProvidersTests/Identities/SyncableIdentitiesValidationTests.swift` (14 tests, 150 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/Subscription/PerformanceOptimizedPaywallsProviderTests.swift` (7 tests, 158 loc) +- `SharedPackages/BrowserServicesKit/Tests/RemoteMessagingTests/RemoteMessagingProcessingTests.swift` (4 tests, 169 loc) +- `SharedPackages/BrowserServicesKit/Tests/MaliciousSiteProtectionTests/MaliciousSiteProtectionAPIClientTests.swift` (7 tests, 183 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/Subscription/PartnershipsHubProviderTests.swift` (11 tests, 187 loc) +- `SharedPackages/BrowserServicesKit/Tests/RemoteMessagingTests/Matchers/CommonAppAttributeMatcherTests.swift` (20 tests, 187 loc) +- `SharedPackages/BrowserServicesKit/Tests/RemoteMessagingTests/Matchers/DesktopUserAttributeMatcherTests.swift` (23 tests, 243 loc) +- `SharedPackages/BrowserServicesKit/Tests/SubscriptionTests/Managers/SubscriptionTierOptionsProviderTests.swift` (11 tests, 251 loc) +- `SharedPackages/BrowserServicesKit/Tests/RemoteMessagingTests/Matchers/MobileUserAttributeMatcherTests.swift` (25 tests, 273 loc) +- `SharedPackages/BrowserServicesKit/Tests/DDGSyncTests/PairingV2/PairingV2MessageExchangerTests.swift` (16 tests, 289 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/SecureVault/SecureVaultTests.swift` (21 tests, 426 loc) +- `SharedPackages/BrowserServicesKit/Tests/RemoteMessagingTests/Matchers/CommonUserAttributeMatcherTests.swift` (80 tests, 565 loc) +- `SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/DataImport/SecureVaultCreditCardImporterTests.swift` (15 tests, 656 loc) +- `SharedPackages/BrowserServicesKit/Tests/RemoteMessagingTests/RemoteMessagingConfigMatcherTests.swift` (44 tests, 1178 loc) + +### SharedPackages/Common/Tests (2) +- `SharedPackages/Common/Tests/CommonTests/AppVersionExtensionTests.swift` (4 tests, 67 loc) +- `SharedPackages/Common/Tests/CommonTests/AppVersionTests.swift` (11 tests, 98 loc) + +### SharedPackages/DataBrokerProtectionCore/Tests (9) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/ScanWideEventRecorderTests.swift` (7 tests, 177 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/MismatchCalculatorUseCaseTests.swift` (5 tests, 194 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/DataBrokerProtectionEngagementPixelsTests.swift` (12 tests, 202 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/RecordFoundDateResolverTests.swift` (8 tests, 285 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/BrokerProfileJobProviderTests.swift` (7 tests, 339 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/DataBrokerProtectionStageDurationCalculatorTests.swift` (17 tests, 365 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/MapperToModelTests.swift` (10 tests, 386 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/LocalBrokerJSONServiceTests.swift` (17 tests, 465 loc) +- `SharedPackages/DataBrokerProtectionCore/Tests/DataBrokerProtectionCoreTests/DataBrokerProtectionEventPixelsTests.swift` (16 tests, 490 loc) + +### SharedPackages/DebugServer/Tests (1) +- `SharedPackages/DebugServer/Tests/DebugServerTests/RouteMatchingTests.swift` (4 tests, 59 loc) + +### SharedPackages/Networking/Tests (2) +- `SharedPackages/Networking/Tests/NetworkingTests/Auth/TokenRefresherTests.swift` (14 tests, 241 loc) +- `SharedPackages/Networking/Tests/NetworkingTests/Auth/OAuthClientTests.swift` (28 tests, 582 loc) + +### SharedPackages/WebExtensions/Tests (6) +- `SharedPackages/WebExtensions/Tests/WebExtensionsTests/Scriptlets/ScriptletSignatureValidatorTests.swift` (7 tests, 142 loc) +- `SharedPackages/WebExtensions/Tests/WebExtensionsTests/WebExtensionAPICompatibilityMessageHandlerTests.swift` (12 tests, 146 loc) +- `SharedPackages/WebExtensions/Tests/WebExtensionsTests/WebExtensionImportScriptsShimTests.swift` (6 tests, 180 loc) +- `SharedPackages/WebExtensions/Tests/WebExtensionsTests/InstalledWebExtensionStoreTests.swift` (16 tests, 224 loc) +- `SharedPackages/WebExtensions/Tests/WebExtensionsTests/WebExtensionAPICompatibilityScriptTests.swift` (12 tests, 252 loc) +- `SharedPackages/WebExtensions/Tests/WebExtensionsTests/WebExtensionAPIStubScriptTests.swift` (95 tests, 1783 loc) + +### iOS/DuckDuckGoTests (31) +- `iOS/DuckDuckGoTests/UniversalOmniBarStateTests.swift` (0 tests, 39 loc) +- `iOS/DuckDuckGoTests/SearchToken/SearchTokenExperimentTests.swift` (3 tests, 54 loc) +- `iOS/DuckDuckGoTests/DaxDialogsOnboardingMigratorTests.swift` (2 tests, 62 loc) +- `iOS/DuckDuckGoTests/PageRefreshMonitor/PageRefreshMonitorExtensionTests.swift` (4 tests, 66 loc) +- `iOS/DuckDuckGoTests/FaviconRequestModifierTests.swift` (1 tests, 74 loc) +- `iOS/DuckDuckGoTests/DataImport/DataImportEntryPointHandlerTests.swift` (4 tests, 74 loc) +- `iOS/DuckDuckGoTests/AutofillNeverPromptWebsitesManagerTests.swift` (5 tests, 85 loc) +- `iOS/DuckDuckGoTests/AtbAndVariantCleanupTests.swift` (4 tests, 92 loc) +- `iOS/DuckDuckGoTests/OnboardingSearchExperiencePickerViewModelTests.swift` (5 tests, 105 loc) +- `iOS/DuckDuckGoTests/AutofillSurveyManagerTests.swift` (5 tests, 118 loc) +- `iOS/DuckDuckGoTests/DaxEasterEggLogos/DaxEasterEggLogoCacheTests.swift` (7 tests, 143 loc) +- `iOS/DuckDuckGoTests/Subscription/FreeTrials/VPNSubscriptionPromotionHelperTests.swift` (8 tests, 148 loc) +- `iOS/DuckDuckGoTests/SwitchBar/SessionStateMetricsTests.swift` (8 tests, 154 loc) +- `iOS/DuckDuckGoTests/BrowsingMenu/BrowsingMenuHeaderDataSourceTests.swift` (13 tests, 164 loc) +- `iOS/DuckDuckGoTests/Telemetry/ProductSurfaceTelemetryTests.swift` (17 tests, 175 loc) +- `iOS/DuckDuckGoTests/Autocomplete/AutocompleteSuggestionsPixelsTests.swift` (17 tests, 178 loc) +- `iOS/DuckDuckGoTests/Subscription/Onboarding/SubscriptionOnboardingFreeTrialCalendarCardModelTests.swift` (13 tests, 179 loc) +- `iOS/DuckDuckGoTests/AIChat/AIChatAddressBarExperienceTests.swift` (12 tests, 183 loc) +- `iOS/DuckDuckGoTests/BrowsingMenu/BrowsingMenuHeaderStateProviderTests.swift` (10 tests, 184 loc) +- `iOS/DuckDuckGoTests/TabSwitcherBarsStateHandlerTests.swift` (22 tests, 210 loc) +- `iOS/DuckDuckGoTests/LongPressBarMenuBuilderTests.swift` (14 tests, 219 loc) +- `iOS/DuckDuckGoTests/Onboarding/OnboardingSubscriptionPromotionHelpingTests.swift` (12 tests, 221 loc) +- `iOS/DuckDuckGoTests/OnboardingSearchExperienceSettingsResolverTests.swift` (13 tests, 234 loc) +- `iOS/DuckDuckGoTests/AIChat/IPadModeToggleTextModelTests.swift` (27 tests, 251 loc) +- `iOS/DuckDuckGoTests/Subscription/SimplifiedPaywall/SubscriptionPagesUseSubscriptionFeatureSimplifiedPaywallTests.swift` (10 tests, 273 loc) +- `iOS/DuckDuckGoTests/AppURLsTests.swift` (42 tests, 367 loc) +- `iOS/DuckDuckGoTests/AIChat/NewAddressBarPickerDisplayValidatorTests.swift` (20 tests, 371 loc) +- `iOS/DuckDuckGoTests/Subscription/Onboarding/SubscriptionOnboardingExperimentTests.swift` (35 tests, 393 loc) +- `iOS/DuckDuckGoTests/TabSwitcherMenuBuilderTests.swift` (55 tests, 622 loc) +- `iOS/DuckDuckGoTests/SmallOmniBarStateTests.swift` (76 tests, 781 loc) +- `iOS/DuckDuckGoTests/LargeOmniBarStateTests.swift` (82 tests, 790 loc) + +### iOS/LocalPackages/AppRouting (1) +- `iOS/LocalPackages/AppRouting/Tests/AppRoutingTests/SearchURLBuilderTests.swift` (15 tests, 149 loc) + +### iOS/WebViewUnitTests (1) +- `iOS/WebViewUnitTests/UserAgentTests.swift` (37 tests, 572 loc) + +### macOS/LocalPackages/AddressBarPerformance (1) +- `macOS/LocalPackages/AddressBarPerformance/Tests/AddressBarPerformanceTests/AddressBarPerformanceRecorderTests.swift` (18 tests, 256 loc) + +### macOS/LocalPackages/AppUpdater (5) +- `macOS/LocalPackages/AppUpdater/Tests/AppStoreAppUpdaterTests/AppStoreUpdaterAvailabilityCheckerTests.swift` (1 tests, 39 loc) +- `macOS/LocalPackages/AppUpdater/Tests/AppUpdaterSharedTests/ManualUpdateRemovalHandlerTests.swift` (4 tests, 86 loc) +- `macOS/LocalPackages/AppUpdater/Tests/SparkleAppUpdaterTests/ApplicationUpdateDetectorTests.swift` (19 tests, 154 loc) +- `macOS/LocalPackages/AppUpdater/Tests/SparkleAppUpdaterTests/SparkleUpdateCompletionValidatorTests.swift` (9 tests, 393 loc) +- `macOS/LocalPackages/AppUpdater/Tests/SparkleAppUpdaterTests/SparkleUpdateWideEventTests.swift` (39 tests, 689 loc) + +### macOS/LocalPackages/DataBrokerProtection-macOS (2) +- `macOS/LocalPackages/DataBrokerProtection-macOS/Tests/DataBrokerProtection-macOSTests/BrokerProfileJobEventsHandlerTests.swift` (5 tests, 101 loc) +- `macOS/LocalPackages/DataBrokerProtection-macOS/Tests/DataBrokerProtection-macOSTests/DataBrokerProtectionWebUIPixelsTests.swift` (9 tests, 170 loc) + +### macOS/LocalPackages/HistoryView (1) +- `macOS/LocalPackages/HistoryView/Tests/HistoryViewTests/DataClientTests.swift` (8 tests, 123 loc) + +### macOS/LocalPackages/SyncUI-macOS (1) +- `macOS/LocalPackages/SyncUI-macOS/Tests/SyncUI-macOSTests/RecoveryCodeViewModelTests.swift` (3 tests, 56 loc) + +### macOS/UnitTests (29) +- `macOS/UnitTests/App/DockPositionProviderTests.swift` (1 tests, 48 loc) +- `macOS/UnitTests/NewTabPage/NewTabPageSectionsAvailabilityProviderTests.swift` (2 tests, 53 loc) +- `macOS/UnitTests/DefaultBrowserAndAddToDockPrompts/DefaultBrowserAndDockPromptServiceTests.swift` (1 tests, 63 loc) +- `macOS/UnitTests/PageRefreshMonitor/PageRefreshMonitorExtensionTests.swift` (4 tests, 63 loc) +- `macOS/UnitTests/WebExtensions/WebExtensionPageStubUserScriptTests.swift` (7 tests, 70 loc) +- `macOS/UnitTests/Statistics/ATB/AtbAndVariantCleanupTests.swift` (3 tests, 76 loc) +- `macOS/UnitTests/Bookmarks/ViewModels/BookmarksBarPromptViewModelTests.swift` (4 tests, 76 loc) +- `macOS/UnitTests/History/View/HistoryViewDataProviderPixelHandlerTests.swift` (5 tests, 81 loc) +- `macOS/UnitTests/Statistics/ATB/DefaultAttributedMetricOriginProviderTests.swift` (5 tests, 84 loc) +- `macOS/UnitTests/NetworkProtection/DefaultVPNLocationFormatterTests.swift` (2 tests, 86 loc) +- `macOS/UnitTests/DataImport/DataDirectoryPermissionFixAvailabilityTests.swift` (6 tests, 91 loc) +- `macOS/UnitTests/AIChat/DuckAiVoiceChatPermissionOverrideTests.swift` (6 tests, 102 loc) +- `macOS/UnitTests/AIChat/AIChatUsageWarningRingViewTests.swift` (6 tests, 109 loc) +- `macOS/UnitTests/Onboarding/OnboardingChromeExtensionExperimentTests.swift` (5 tests, 111 loc) +- `macOS/UnitTests/Subscription/SubscriptionRedirectManagerTests.swift` (4 tests, 114 loc) +- `macOS/UnitTests/AIChat/AIChatMenuButtonPixelsTests.swift` (9 tests, 116 loc) +- `macOS/UnitTests/Statistics/ATB/InstallationAttributionPixelHandlerTests.swift` (5 tests, 120 loc) +- `macOS/UnitTests/Updates/AppStoreUpdateMenuItemFactoryTests.swift` (6 tests, 125 loc) +- `macOS/UnitTests/Tab/ViewModel/UnloadedTabViewModelTests.swift` (7 tests, 133 loc) +- `macOS/UnitTests/NewTabPage/NewTabPageProtectionsReportSettingsMigratorTests.swift` (19 tests, 142 loc) +- `macOS/UnitTests/AIChat/EphemeralPromptDraftStoreTests.swift` (10 tests, 145 loc) +- `macOS/UnitTests/AIChat/DuckAiVoiceChatShieldScopeTests.swift` (12 tests, 148 loc) +- `macOS/UnitTests/Onboarding/OnboardingNonBlockingExperimentTests.swift` (11 tests, 217 loc) +- `macOS/UnitTests/QuitSurvey/QuitSurveyReturnUserHandlerTests.swift` (14 tests, 221 loc) +- `macOS/UnitTests/Fire/DataClearingWideEventServiceTests.swift` (12 tests, 296 loc) +- `macOS/UnitTests/NewTabPage/NewTabPageNextStepsCardsPersistorTests.swift` (36 tests, 353 loc) +- `macOS/UnitTests/UserChurn/UserChurnServiceTests.swift` (23 tests, 380 loc) +- `macOS/UnitTests/NewTabPage/NewTabPageNextStepsCardsPixelHandlerTests.swift` (25 tests, 426 loc) +- `macOS/UnitTests/AIChat/AIChatSessionStoreTests.swift` (36 tests, 634 loc)