feat: заготовки подбора голоса Лены сохранены и возвращаются после перезагрузки

Оснастка подбора голоса жила только в /tmp на машине робота, а /tmp вычищается
целиком при каждой перезагрузке правилом D /tmp. Машина не перезагружалась 31
сутки — в день перезагрузки работа исчезла бы молча. Решение владельца Р107.

Что сделано:
- 43 файла заготовок положены в bots/lena-golos/zagotovki — 4 файла подставного
  собеседника chelovek* и 39 в восьми папках подбора. Перенос поимённым
  перечнем; записи разговоров не тронуты ни одной;
- постоянная копия на машине в /home/ubuntu/lena-inworld/zagotovki;
- правило воссоздания /etc/tmpfiles.d/lena-zagotovki.conf, 12 строк C,
  эталон в bots/lena-golos/lena-zagotovki.conf, побайтово равен серверному;
- сторож proverka-zagotovok.sh — 21 проверка: хранилище по слепкам, машина
  побайтово, текст правила, и живой опыт воссоздания на подставном дереве;
- слепки всех 43 файлов в zagotovki-slepki.sha256;
- раздел в README про заготовки и возврат руками.

Замерено, а не предположено: порядок фаз systemd-tmpfiles — стирание идёт
раньше создания в одном вызове теми же ключами, какими зовёт загрузка. Правило
лечит только полное отсутствие: частичную порчу оно пропускает молча — записано
в самом правиле, в README и ловится сторожем.

Сторож показан красным шесть раз и на красном поймал два ложных зелёных в самом
себе: пустой ответ машины и пустое правило выглядели успехом. Оба вылечены.

Мерки: Obzvon 214 из 214, 958 утверждений, красных 0.
Сторожа машины 26, 46, 27 — красных 0, число проверок не уменьшилось.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-08-06 16:58:23 +03:00
parent ef10ef6ec6
commit 588f05aef1
48 changed files with 1043 additions and 1 deletions
+151 -1
View File
@@ -26,6 +26,10 @@
| `chistka-sleda-prob.cron` | копия расписания этой уборки с сервера — эталон |
| `proba-mosta-bez-zvonka.py` | проба моста подставными посылками Inworld, **без звонка** |
| `proverka-tekstovogo-sleda.sh` | сторож текстового следа — по каждому из четырёх мест отдельно |
| `zagotovki/` | сами заготовки подбора голоса — 43 файла, снятые с машины робота |
| `zagotovki-slepki.sha256` | слепок каждой заготовки — по нему видно не только пропажу, но и порчу |
| `lena-zagotovki.conf` | копия правила, которое возвращает заготовки в `/tmp` при загрузке — эталон |
| `proverka-zagotovok.sh` | сторож заготовок: хранилище, машина, правило и живой опыт воссоздания |
## Ключ
@@ -444,6 +448,149 @@ cd ~/lena-inworld && venv/bin/python proba-mosta-bez-zvonka.py
считаются от своей даты, 03.08.2026, — уйдут около **01.11.2026**. До тех дней
они лежат, и это не поломка, а решение владельца.
## Заготовки подбора голоса
**Что это.** Оснастка, на которой подбирали, как Лена должна звучать. Не записи
разговоров — этим *искали* голос:
| что | сколько | зачем |
|---|---|---|
| `chelovek.wav`, `chelovek-nomer.wav`, `chelovek-dorogo.wav` | 3 | реплики подставного собеседника для проб без телефонии |
| `chelovek.syroy` | 1 | сырьё, из которого их делали |
| `obraztsy/` | 9 | образцы женских голосов — из чего выбирали |
| `tempy/`, `tempy2/` | 7 + 6 | одна фраза на разных скоростях речи |
| `udarenie/` | 4 | четыре способа поставить ударение |
| `dyhanie/`, `zhivost/` | 2 + 7 | вдохи и «живость» — чтобы речь не звучала машинной |
| `brend/`, `itog/` | 2 + 2 | «было / стало» — чем кончился подбор |
Всего **43 файла, 4 773 060 байт**. Лежат в `bots/lena-golos/zagotovki/`.
### 🔴 Почему они здесь, а не только на машине
Они жили **только** в `/tmp/lena-inworld/` на машине робота. А `/tmp`
вычищается **целиком при каждой перезагрузке** правилом `D /tmp` из
`/usr/lib/tmpfiles.d/tmp.conf`. Машина не перезагружалась с 05.07.2026 —
на 06.08.2026 это 31 сутки. В день, когда её перезагрузят по любой причине —
обновление, сбой, что угодно, — вся эта работа исчезла бы **молча**, и никто
не заметил бы, пока она не понадобится.
Шесть файлов из `tempy2/` восстановить было бы нечем **вовсе**: скрипта,
который их делал, больше не существует.
Решение владельца **Р107 от 06.08.2026**: положить заготовки в хранилище все
до одной и завести того, кто вернёт их на машину после перезагрузки.
### Как они возвращаются на машину сами
Двумя вещами, обе на сервере `liderra-render` (51.250.1.97):
| что | где на сервере |
|---|---|
| постоянная копия | `/home/ubuntu/lena-inworld/zagotovki/` |
| правило воссоздания | `/etc/tmpfiles.d/lena-zagotovki.conf` |
Постоянная копия лежит в `/home`, а не в `/tmp`, — она перезагрузку переживает.
Правило при каждой загрузке копирует из неё в `/tmp/lena-inworld` то, чего там
нет. Зовёт его штатная служба `systemd-tmpfiles-setup`, та же самая, что уже
воссоздаёт саму папку записей.
🪤 **В той же папке `~/lena-inworld/` работает ежесуточная уборка**
`chistka-sleda-prob.sh`. Заготовки она не тронет — замерено: она берёт
`find -maxdepth 1 -type f -name 'progon-*.txt'`, то есть в подпапки не
спускается и смотрит только на `.txt`. Но знать об этом надо: класть что-то
ещё в эту папку без такой проверки нельзя.
### 🔴 Чего это НЕ лечит — три молчания
Правило возвращает файл, только если его **нет вообще**. Замерено опытами на
подставном дереве:
| случай | что будет |
|---|---|
| папка на месте, но внутри пропал один файл из двух | второй **не вернётся**, ругани не будет |
| файл на месте, но пустой, ноль байт | он **не будет перезаписан** |
| постоянной копии на машине нет вовсе | при загрузке ругань уйдёт в журнал, который никто не читает |
Для беды, ради которой всё заводилось, этого хватает: перезагрузка стирает
**всё**, значит все цели исчезают целиком и возвращаются целиком. Но для
частичной порчи **единственное, что об этом когда-либо скажет, — сторож**.
### Как проверить, что заготовки целы
```bash
bash bots/lena-golos/proverka-zagotovok.sh
```
21 проверка, и она **называет поимённо**, что именно пропало или испортилось.
Смотрит на четыре вещи, каждая из которых по отдельности была бы слепой:
1. **хранилище** — все 43 файла на месте и сходятся со слепками. Не «файл
есть», а «файл тот же»: пустой файл в ноль байт лежит на месте и выглядит
целым, ловится только слепком;
2. **машина** — доехала ли постоянная копия, сверка **побайтово** и **на самой
машине**, а не по тому, что вернулось по сети;
3. **правило** — на месте ли, совпадает ли текст с хранилищем, читает ли его
systemd без жалоб, и обещает ли оно вернуть **ровно то**, что лежит в
заготовках. Завели девятую папку, а строку дописать забыли — сторож скажет;
4. **живой опыт** — правило вправду применяется. Не «текст выглядит
правильным», а прогон: настоящий текст правила, настоящий источник,
подменён только столбец «куда», и все 43 файла обязаны вернуться.
🔴 **Перезагружать машину ради проверки нельзя** — на ней живёт рендер боевого
«Поиска клиентов». Поэтому опыт идёт на подставной папке
`/tmp/lena-proba-zagotovok-storozh`, которую сторож сам создаёт и за собой
убирает. Живую папку он не трогает и не удаляет из неё ничего.
Если машина занята и трогать её не хочется — часть с опытом можно пропустить:
```bash
LENA_BEZ_OPYTA=da bash bots/lena-golos/proverka-zagotovok.sh
```
🔴 **Сторожа надо кому-то запускать.** Сам он не ходит — ровно тот же открытый
хвост, что и у сторожа уборки.
### Как вернуть заготовки на машину руками
Обычно этого делать не надо — правило справляется само при загрузке. Но если
понадобилось (правило снесли, постоянную копию потеряли, машину поменяли):
```bash
# 1. постоянная копия — из хранилища на машину
scp -r bots/lena-golos/zagotovki ubuntu@51.250.1.97:/home/ubuntu/lena-inworld/
# 2. правило воссоздания
scp bots/lena-golos/lena-zagotovki.conf ubuntu@51.250.1.97:/tmp/novoe-pravilo-zagotovok
ssh ubuntu@51.250.1.97 "sudo install -m 644 -o root -g root /tmp/novoe-pravilo-zagotovok /etc/tmpfiles.d/lena-zagotovki.conf && rm -f /tmp/novoe-pravilo-zagotovok"
# 3. применить, не дожидаясь перезагрузки
ssh ubuntu@51.250.1.97 "sudo systemd-tmpfiles --create /etc/tmpfiles.d/lena-zagotovki.conf"
# 4. убедиться
bash bots/lena-golos/proverka-zagotovok.sh
```
🔴 Шаг 3 вернёт только то, чего в `/tmp/lena-inworld` **нет**. Если файл там
лежит, но испорчен, правило его не тронет — такой файл придётся положить
руками, поимённо.
🪤 Если рабочее дерево на Windows подставило `\r`, правило поедет на сервер с
лишними символами и побайтовая сверка покраснеет. Снимать при переносе:
`tr -d '\r' < файл`.
### Как сверить руками, без сторожа
```bash
# хранилище
cd bots/lena-golos/zagotovki && sha256sum -c ../zagotovki-slepki.sha256 | grep -v ': OK$'
# машина
grep '^[0-9a-f]' bots/lena-golos/zagotovki-slepki.sha256 \
| ssh ubuntu@51.250.1.97 "cd /home/ubuntu/lena-inworld/zagotovki && sha256sum -c" | grep -v ': OK$'
```
Обе команды обязаны не напечатать **ничего**.
## Как сверить копию настройки с сервером
`extensions-lena.conf` — копия `/etc/asterisk/extensions.conf` с сервера
@@ -588,4 +735,7 @@ $ sed 's/;.*//' bots/lena-golos/extensions-lena.conf | grep -c ZAPIS_RAZGOVOROV
<!-- Имена файлов и папок на сервере записаны транслитом. В общий словарь
проекта их не тащим — они живут только здесь. -->
<!-- cspell:ignore chelovek syroy brend dyhanie obraztsy tempy udarenie zhivost chistka zapisey proverka vhodyashchie ZHURNAL SROK CHASOV PAPKA ZAPIS RAZGOVOROV dialplan globals nabor proba mango vhodyashchiy priyomshchica imya lena golos storozh mosta tishina most metodika klienta chistki raspisaniya journalctl tekstovyy sled logrotate copytruncate notifempty maxage dateext nocompress progon prob terpenie mozgi svezhiy podpapka kluch rech cheloveka tekstovogo sleda челове -->
<!-- cspell:ignore chelovek syroy brend dyhanie obraztsy tempy udarenie zhivost chistka zapisey proverka vhodyashchie ZHURNAL SROK CHASOV PAPKA ZAPIS RAZGOVOROV dialplan globals nabor proba mango vhodyashchiy priyomshchica imya lena golos storozh mosta tishina most metodika klienta chistki raspisaniya journalctl tekstovyy sled logrotate copytruncate notifempty maxage dateext nocompress progon prob terpenie mozgi svezhiy podpapka kluch rech cheloveka tekstovogo sleda челове zagotovki zagotovok slepki vosstanovleniya postoyanno zacepka Alyona Snezhana skorost bylo stalo vdoh bodro teplo defis prosto zaglavno znak dorogo nomer -->
<!-- 🪤 Слова обязаны стоять на ОДНОЙ строке с cspell:ignore: указание читается
до конца строки, и перенесённые вниз слова не только не попадают в
словарь, но и сами становятся ошибками. Замерено 06.08.2026. -->
+88
View File
@@ -0,0 +1,88 @@
# ============================================================================
# ЗАГОТОВКИ ПОДБОРА ГОЛОСА ЛЕНЫ — воссоздание после перезагрузки. Эталон.
#
# Куда кладётся: сервер liderra-render 51.250.1.97, файл
# /etc/tmpfiles.d/lena-zagotovki.conf — под тем же именем.
# Когда: 06.08.2026, решение владельца Р107.
#
# 🔴 ЗАЧЕМ ЭТО ВООБЩЕ НУЖНО. В папке /tmp/lena-inworld лежат не только записи
# разговоров. Там же лежит ОСНАСТКА, на которой подбирали, как Лена должна
# звучать: четыре заготовки подставного собеседника chelovek* и восемь папок
# проб — голоса, темпы речи, ударения, дыхание, живость, бренд, итог.
# Это не записи. Это работа, которую делали руками много часов.
#
# А /tmp вычищается ЦЕЛИКОМ при каждой перезагрузке машины — правилом
# «D /tmp 1777 root root -» из /usr/lib/tmpfiles.d/tmp.conf. Буква D значит
# «то же, что d, но вдобавок удалить содержимое, когда зовут с --remove», а
# при загрузке зовут именно так:
# ExecStart=systemd-tmpfiles --create --remove --boot --exclude-prefix=/dev
#
# Машина не перезагружалась с 05.07.2026 — 31 сутки на 06.08.2026. В день,
# когда её перезагрузят по любой причине, вся оснастка исчезнет МОЛЧА, и
# никто этого не заметит, пока она не понадобится. Шесть файлов из папки
# tempy2 восстановить было бы нечем вовсе: скрипта, который их делал, больше
# не существует.
#
# Здесь заготовки возвращаются на место сами, при загрузке машины, из
# постоянной копии, которая перезагрузку переживает.
#
# 🔴 ПОЧЕМУ НЕ ПРОЩЕ — четыре «а нельзя ли иначе», и почему нельзя.
#
# «А не перенести ли заготовки насовсем в /home и не трогать /tmp?»
# Нельзя. Путь /tmp/lena-inworld/chelovek прошит в настройке телефонии
# (/etc/asterisk/extensions.conf, раздел [chelovek-proba]) и ещё в семи
# скриптах проб. Ради уборки пришлось бы править работающую телефонию и
# живой мост. Трогать работающее ради порядка — не наш размен.
#
# «А не проще ли ссылку вместо копии?» Нет, и это ловушка. Если постоянная
# копия однажды пропадёт, ссылка останется — и файл будет ВИДЕН, а
# прочитаться не сможет. Пропажа притворится наличием. Копия так не умеет:
# нет копии — нет файла, и это сразу видно.
#
# «А не проще ли копировать по расписанию раз в час?» Нет. Машина делит
# железо с боевым рендером «Поиска клиентов» (решение владельца Р84 —
# лишней нагрузки не давать), а болезнь тут не «файлы портятся понемногу»,
# а «всё исчезает разом при загрузке». Лечить надо ровно в тот момент.
#
# «А не проще ли ОДНА строка на всю папку /tmp/lena-inworld?» Нет — и это
# не вкусовщина, а замер. Строка C копирует папку, только если цель не
# существует ИЛИ пуста. Папку /tmp/lena-inworld после перезагрузки может
# создать Asterisk первой же записью разговора — и тогда одна общая строка
# была бы пропущена ЦЕЛИКОМ и молча, вместе со всеми заготовками. Поэтому
# строк двенадцать: каждая про свою вещь, и чужая запись рядом им не мешает.
#
# 🔴 ЧЕГО ЭТО ПРАВИЛО НЕ УМЕЕТ — знать обязательно.
# Оно лечит ТОЛЬКО полное отсутствие. Замерено на подставном дереве:
# - папка на месте, но внутри пропал один файл из двух → второй НЕ
# вернётся, и ругани не будет;
# - файл на месте, но пустой, ноль байт → он НЕ будет перезаписан;
# - постоянной копии нет вовсе → при загрузке будет ругань в журнале,
# которую никто не читает.
# Ровно для беды, ради которой правило заведено (перезагрузка стирает ВСЁ),
# этого достаточно: все цели исчезают целиком. Для всего остального сторож
# proverka-zagotovok.sh — единственное, что об этом когда-либо скажет.
#
# 🔴 ПОЧЕМУ ОТДЕЛЬНЫЙ ФАЙЛ, А НЕ СТРОКИ В lena-inworld.conf. Тот файл про права
# на папку записей, этот — про оснастку проб. Разные дела, разные сроки
# жизни, и сверяются они разными сторожами. Порядок имён файлов на дело не
# влияет — замерено: заготовки возвращаются и когда этот файл читается
# первым.
#
# 🔴 Текст на сервере и здесь обязан совпадать ПОБАЙТОВО. Чем сверять —
# README.md, раздел «Заготовки подбора голоса».
#
# Столбцы: что делаем | куда | права | владелец | группа | срок | откуда брать
# ============================================================================
C /tmp/lena-inworld/chelovek.wav - - - - /home/ubuntu/lena-inworld/zagotovki/chelovek.wav
C /tmp/lena-inworld/chelovek-nomer.wav - - - - /home/ubuntu/lena-inworld/zagotovki/chelovek-nomer.wav
C /tmp/lena-inworld/chelovek-dorogo.wav - - - - /home/ubuntu/lena-inworld/zagotovki/chelovek-dorogo.wav
C /tmp/lena-inworld/chelovek.syroy - - - - /home/ubuntu/lena-inworld/zagotovki/chelovek.syroy
C /tmp/lena-inworld/brend - - - - /home/ubuntu/lena-inworld/zagotovki/brend
C /tmp/lena-inworld/dyhanie - - - - /home/ubuntu/lena-inworld/zagotovki/dyhanie
C /tmp/lena-inworld/itog - - - - /home/ubuntu/lena-inworld/zagotovki/itog
C /tmp/lena-inworld/obraztsy - - - - /home/ubuntu/lena-inworld/zagotovki/obraztsy
C /tmp/lena-inworld/tempy - - - - /home/ubuntu/lena-inworld/zagotovki/tempy
C /tmp/lena-inworld/tempy2 - - - - /home/ubuntu/lena-inworld/zagotovki/tempy2
C /tmp/lena-inworld/udarenie - - - - /home/ubuntu/lena-inworld/zagotovki/udarenie
C /tmp/lena-inworld/zhivost - - - - /home/ubuntu/lena-inworld/zagotovki/zhivost
+346
View File
@@ -0,0 +1,346 @@
#!/bin/bash
# Сторож ЗАГОТОВОК ПОДБОРА ГОЛОСА.
#
# Что он охраняет. Оснастку, на которой подбирали, как Лена должна звучать:
# четыре заготовки подставного собеседника chelovek* и восемь папок проб
# (brend, dyhanie, itog, obraztsy, tempy, tempy2, udarenie, zhivost) — всего
# 43 файла. Они лежали ТОЛЬКО в /tmp на машине робота, а /tmp вычищается
# целиком при каждой перезагрузке. 06.08.2026 они положены в хранилище
# (решение владельца Р107), а на машину возвращаются сами — правилом
# /etc/tmpfiles.d/lena-zagotovki.conf.
#
# 🔴 Чем он отличается от «файлы вроде на месте». Он смотрит на ЧЕТЫРЕ вещи
# сразу, и каждая по отдельности была бы слепой:
# 1. хранилище — целы ли заготовки здесь, ПОФАЙЛОВО и по слепку;
# 2. машина — доехала ли постоянная копия туда, ПОБАЙТОВО;
# 3. правило — на месте ли оно и то ли в нём написано;
# 4. живой опыт — вправду ли правило возвращает заготовки. Не «текст
# выглядит правильным», а прогон на подставном дереве.
#
# 🔴 Живую папку /tmp/lena-inworld сторож не трогает и не удаляет из неё
# ничего. Опыт идёт на своей папке /tmp/lena-proba-zagotovok-storozh,
# которую он сам создаёт и за собой убирает.
#
# 🔴 Он называет ПОИМЁННО, что именно пропало или испортилось, а не просто
# краснеет. Сторож, который говорит «что-то не так», не помогает никому.
#
# Запуск одной командой:
# bash bots/lena-golos/proverka-zagotovok.sh
#
# Настройки — переменными окружения:
# LENA_SERVER куда ходить (по умолчанию ubuntu@51.250.1.97)
# LENA_KLYUCH каким ключом (по умолчанию ~/.ssh/liderra_deploy)
# LENA_BEZ_OPYTA=da — пропустить живой опыт (часть 3), если машина занята
#
# Возвращает 0, если всё цело, и 1, если хоть одна проверка покраснела.
set -u
ZDES="$(cd "$(dirname "$0")" && pwd)"
ZAGOTOVKI="$ZDES/zagotovki"
SLEPKI="$ZDES/zagotovki-slepki.sha256"
ETALON_PRAVILA="$ZDES/lena-zagotovki.conf"
SERVER="${LENA_SERVER:-ubuntu@51.250.1.97}"
KLYUCH="${LENA_KLYUCH:-$HOME/.ssh/liderra_deploy}"
# Пути на сервере. Меняются вместе с эталоном, не по отдельности.
PAPKA_TAM="/tmp/lena-inworld"
POSTOYANNO_TAM="/home/ubuntu/lena-inworld/zagotovki"
PRAVILO_TAM="/etc/tmpfiles.d/lena-zagotovki.conf"
# 🔴 Имя нарочно длинное и явно временное — чтобы его нельзя было спутать ни с
# чем живым, и чтобы «убрать за собой» било только по своему.
PROBA_TAM="/tmp/lena-proba-zagotovok-storozh"
PROVALOV=0
PROVERENO=0
zelenyy() { PROVERENO=$(( PROVERENO + 1 )); echo "$1"; }
krasnyy() { PROVERENO=$(( PROVERENO + 1 )); PROVALOV=$(( PROVALOV + 1 )); echo " 🔴 $1"; }
na_servere() { ssh -o BatchMode=yes -o ConnectTimeout=20 -i "$KLYUCH" "$SERVER" "$1" 2>/dev/null; }
itog_i_vyyti() {
echo "──────────────────────────────────────────────"
if [ "$PROVALOV" -eq 0 ]; then
echo "✅ ЦЕЛО: проверок $PROVERENO, красных 0"
exit 0
fi
echo "🔴 БЕДА: проверок $PROVERENO, красных $PROVALOV"
exit 1
}
# Образец имени записи разговора. Такие файлы — персональные данные, и в
# заготовках их быть не может НИКОГДА.
OBRAZETS_ZAPISI='^(vh-)?[0-9]{9,}\.[0-9]+\.wav$'
echo "Сторож заготовок подбора голоса."
echo
# ==================================================== ЧАСТЬ 1 — ХРАНИЛИЩЕ ===
echo "1. Хранилище: целы ли заготовки здесь"
if [ -f "$SLEPKI" ]; then
zelenyy "перечень слепков на месте: zagotovki-slepki.sha256"
else
krasnyy "перечня слепков НЕТ: $SLEPKI — сверять не с чем, дальше мерить нечем"
itog_i_vyyti
fi
if [ -d "$ZAGOTOVKI" ]; then
zelenyy "папка заготовок на месте: bots/lena-golos/zagotovki/"
else
krasnyy "папки заготовок НЕТ ВОВСЕ: $ZAGOTOVKI — пропало всё"
itog_i_vyyti
fi
ZHDYOM=$(grep -c '^[0-9a-f]' "$SLEPKI")
NASHLI=$(find "$ZAGOTOVKI" -type f | wc -l)
if [ "$ZHDYOM" -eq "$NASHLI" ]; then
zelenyy "файлов столько же, сколько слепков: $NASHLI"
else
krasnyy "файлов $NASHLI, а слепков $ZHDYOM — состав разошёлся"
fi
# 🔴 Главная проверка хранилища: не «файл есть», а «файл ТОТ ЖЕ». Пустой файл
# в ноль байт лежит на месте и выглядит целым — ловится только слепком.
NE_SOSHLOS="$( cd "$ZAGOTOVKI" && grep '^[0-9a-f]' "$SLEPKI" | sha256sum -c 2>&1 | grep -v ': OK$' )"
if [ -z "$NE_SOSHLOS" ]; then
zelenyy "все $ZHDYOM заготовок сходятся со слепками — ни одна не пропала и не испортилась"
else
krasnyy "заготовки НЕ СХОДЯТСЯ. Вот что именно:"
echo "$NE_SOSHLOS" | sed 's/^/ | /'
fi
LISHNIE="$( cd "$ZAGOTOVKI" && find . -type f | sed 's#^\./##' | LC_ALL=C sort \
| comm -23 - <(grep '^[0-9a-f]' "$SLEPKI" | sed 's/^[0-9a-f]* //' | LC_ALL=C sort) )"
if [ -z "$LISHNIE" ]; then
zelenyy "лишних файлов в заготовках нет — ни одного без слепка"
else
krasnyy "в заготовках есть файлы БЕЗ слепка — кто-то положил, не записав:"
echo "$LISHNIE" | sed 's/^/ | /'
fi
# 🔴🔴 Ворота персональных данных. В заготовках не может оказаться записи
# настоящего разговора. Если оказалась — это утечка, и круг остановлен.
ZAPISI="$( cd "$ZAGOTOVKI" && find . -type f -printf '%f\n' | grep -E "$OBRAZETS_ZAPISI" )"
if [ -z "$ZAPISI" ]; then
zelenyy "записей разговоров среди заготовок НЕТ — личные данные не утекли"
else
krasnyy "🔴 СРЕДИ ЗАГОТОВОК ЗАПИСЬ РАЗГОВОРА — ЭТО ЛИЧНЫЕ ДАННЫЕ:"
echo "$ZAPISI" | sed 's/^/ | /'
fi
echo
# ============================================= ЧАСТЬ 2 — ЭТАЛОН ПРАВИЛА ====
echo "2. Правило воссоздания: то ли в нём написано"
if [ -f "$ETALON_PRAVILA" ]; then
zelenyy "эталон правила на месте: lena-zagotovki.conf"
else
krasnyy "эталона правила НЕТ: $ETALON_PRAVILA"
itog_i_vyyti
fi
# Что правило обещает вернуть — первый столбец после «C», имя в конце пути.
OBESHCHANO="$( grep '^C ' "$ETALON_PRAVILA" | awk '{print $2}' | sed "s#^$PAPKA_TAM/##" | LC_ALL=C sort )"
# Что вправду лежит верхним ярусом в заготовках.
LEZHIT="$( cd "$ZAGOTOVKI" && find . -maxdepth 1 -mindepth 1 -printf '%f\n' | LC_ALL=C sort )"
if [ "$OBESHCHANO" = "$LEZHIT" ]; then
zelenyy "правило обещает вернуть ровно то, что лежит: $(echo "$LEZHIT" | wc -l) вещей"
else
krasnyy "правило и заготовки РАЗОШЛИСЬ. Обещано без строки — слева, лежит без обещания — справа:"
diff <(echo "$OBESHCHANO") <(echo "$LEZHIT") | sed 's/^/ | /'
fi
# Каждая строка обязана брать источник из постоянной копии, а не откуда попало.
NE_OTTUDA="$( grep '^C ' "$ETALON_PRAVILA" | awk -v p="$POSTOYANNO_TAM/" '$7 !~ "^"p {print $2" ← "$7}' )"
if [ -z "$NE_OTTUDA" ]; then
zelenyy "все строки берут источник из $POSTOYANNO_TAM"
else
krasnyy "строки берут источник НЕ ОТТУДА:"
echo "$NE_OTTUDA" | sed 's/^/ | /'
fi
echo
# ==================================================== ЧАСТЬ 3 — МАШИНА =====
echo "3. Машина робота: доехало ли и живо ли"
if [ "$(na_servere 'echo est')" = "est" ]; then
zelenyy "сервер отвечает"
else
krasnyy "сервер НЕ отвечает — дальше мерить нечем"
itog_i_vyyti
fi
if [ "$(na_servere "test -d '$POSTOYANNO_TAM' && echo est")" = "est" ]; then
zelenyy "постоянная копия на машине на месте: $POSTOYANNO_TAM"
else
krasnyy "постоянной копии на машине НЕТ: $POSTOYANNO_TAM — после перезагрузки возвращать будет НЕЧЕГО"
fi
# Слепки шлём на машину и сверяем ТАМ — так сверяется настоящий файл, а не
# то, что доехало обратно по сети.
#
# 🔴 В конце машина обязана сказать «###ZAMERENO». Без этого слова пустой ответ
# значил бы «всё сошлось», хотя на деле замер не состоялся вовсе — оборвалась
# связь, не нашлась папка, отказал sha256sum. Первый прогон этого сторожа
# показал ровно такой ложный зелёный: папки на машине не было ВООБЩЕ, а
# проверка отвечала «сходятся побайтово».
OTVET_SVERKI="$( grep '^[0-9a-f]' "$SLEPKI" \
| ssh -o BatchMode=yes -o ConnectTimeout=20 -i "$KLYUCH" "$SERVER" \
"if ! cd '$POSTOYANNO_TAM' 2>/dev/null; then echo 'папки нет вовсе: $POSTOYANNO_TAM'; else sha256sum -c 2>&1 | grep -v ': OK\$'; fi; echo '###ZAMERENO'" 2>/dev/null )"
if ! echo "$OTVET_SVERKI" | grep -qF '###ZAMERENO'; then
krasnyy "сверку с машиной ПРОВЕСТИ НЕ УДАЛОСЬ — ответ оборвался, судить не о чем"
else
SVERKA_TAM="$(echo "$OTVET_SVERKI" | grep -vF '###ZAMERENO')"
if [ -z "$SVERKA_TAM" ]; then
zelenyy "все $ZHDYOM заготовок на машине сходятся с хранилищем побайтово"
else
krasnyy "на машине заготовки НЕ СХОДЯТСЯ с хранилищем. Вот что именно:"
echo "$SVERKA_TAM" | sed 's/^/ | /'
fi
fi
if [ "$(na_servere "test -f '$PRAVILO_TAM' && echo est")" = "est" ]; then
zelenyy "правило на машине на месте: $PRAVILO_TAM"
else
krasnyy "правила на машине НЕТ: $PRAVILO_TAM — после перезагрузки заготовки не вернутся"
fi
SHA_TAM="$(na_servere "tr -d '\r' < '$PRAVILO_TAM' 2>/dev/null | sha256sum | cut -d' ' -f1")"
SHA_ZDES="$(tr -d '\r' < "$ETALON_PRAVILA" | sha256sum | cut -d' ' -f1)"
if [ -n "$SHA_TAM" ] && [ "$SHA_TAM" = "$SHA_ZDES" ]; then
zelenyy "текст правила на машине и в хранилище один и тот же"
else
krasnyy "текст правила РАЗОШЁЛСЯ: машина $SHA_TAM, хранилище $SHA_ZDES"
fi
# Тот же приём с «###ZAMERENO»: пустая ругань обязана значить «прочитал и
# промолчал», а не «спросить не вышло».
OTVET_RUGANI="$(na_servere "sudo -n systemd-tmpfiles --create --prefix=/net-takogo-puti-nikogda '$PRAVILO_TAM' 2>&1; echo '###ZAMERENO'")"
if ! echo "$OTVET_RUGANI" | grep -qF '###ZAMERENO'; then
krasnyy "спросить systemd про правило НЕ УДАЛОСЬ — ответ оборвался"
else
RUGAN="$(echo "$OTVET_RUGANI" | grep -vF '###ZAMERENO')"
if [ -z "$RUGAN" ]; then
zelenyy "systemd читает правило без единой жалобы"
else
krasnyy "systemd РУГАЕТСЯ на правило — при загрузке будет то же самое:"
echo "$RUGAN" | sed 's/^/ | /'
fi
fi
# 🔴 Уехала ли машина вперёд хранилища. Появилась девятая папка подбора голоса
# или новая заготовка chelovek* — значит работа идёт, а в хранилище её нет.
CHUZHOE="$(na_servere "cd '$PAPKA_TAM' 2>/dev/null && { find . -maxdepth 1 -mindepth 1 -type d -printf '%f\n'; find . -maxdepth 1 -mindepth 1 -type f -name 'chelovek*' -printf '%f\n'; } | LC_ALL=C sort" \
| comm -23 - <(echo "$LEZHIT") )"
if [ -z "$CHUZHOE" ]; then
zelenyy "на машине не завелось заготовок, которых нет в хранилище"
else
krasnyy "на машине ЕСТЬ заготовки, которых НЕТ в хранилище — работа пропадёт при перезагрузке:"
echo "$CHUZHOE" | sed 's/^/ | /'
fi
echo
# ========================================== ЧАСТЬ 4 — ЖИВОЙ ОПЫТ ===========
if [ "${LENA_BEZ_OPYTA:-net}" = "da" ]; then
echo "4. Живой опыт воссоздания — ПРОПУЩЕН по просьбе (LENA_BEZ_OPYTA=da)"
echo
itog_i_vyyti
fi
echo "4. Живой опыт: вправду ли правило возвращает заготовки"
echo " Перезагружать машину нельзя — на ней боевой рендер. Поэтому правило"
echo " применяется к подставному дереву: настоящий текст правила, настоящий"
echo " источник, подменён только столбец «куда»."
OTVET="$( ssh -o BatchMode=yes -o ConnectTimeout=30 -i "$KLYUCH" "$SERVER" 'bash -s' <<REMOTE 2>&1
set -u
PROBA="$PROBA_TAM"
case "\$PROBA" in
/tmp/lena-proba-zagotovok-storozh) : ;;
*) echo "STOP: не тот путь для уборки: \$PROBA"; exit 9 ;;
esac
sudo -n rm -rf "\$PROBA"
sudo -n mkdir -p "\$PROBA/vremennaya/lena-inworld"
sudo -n chown -R root:root "\$PROBA"
# Мусор, который обязан исчезнуть — доказывает, что стирание идёт РАНЬШЕ
# создания, а не после (иначе заготовки снесло бы вместе с мусором).
printf 'мусор\n' | sudo -n tee "\$PROBA/vremennaya/musor-storozha.txt" >/dev/null
sudo -n mkdir -p "\$PROBA/vremennaya/lena-inworld/podpapka-musora"
# Правило выводим ИЗ НАСТОЯЩЕГО, подменяя только «куда». Источник остаётся
# настоящей постоянной копией — значит опыт проверяет и её тоже.
sudo -n sed -E "s#^C $PAPKA_TAM/#C \$PROBA/vremennaya/lena-inworld/#" "$PRAVILO_TAM" \
| sudo -n tee "\$PROBA/pravilo.conf" >/dev/null
printf 'D %s/vremennaya 1777 root root -\n' "\$PROBA" | sudo -n tee "\$PROBA/steret.conf" >/dev/null
# 🔴 Сколько строк вправду доехало до подставного правила. Пустое правило
# systemd исполняет молча и с кодом 0 — то есть «ничего не делать» выглядит
# как «всё получилось». Первый прогон этого сторожа поймал ровно такой
# ложный зелёный.
echo "STROK=\$(grep -c '^C ' "\$PROBA/pravilo.conf" 2>/dev/null || echo 0)"
sudo -n systemd-tmpfiles --create --remove --boot "\$PROBA/steret.conf" "\$PROBA/pravilo.conf" >/dev/null 2>&1
echo "KOD=\$?"
if [ -e "\$PROBA/vremennaya/musor-storozha.txt" ] || [ -e "\$PROBA/vremennaya/lena-inworld/podpapka-musora" ]; then
echo "MUSOR=ostalsya"
else
echo "MUSOR=snesyon"
fi
echo "RAZNICA<<"
sudo -n diff -r "$POSTOYANNO_TAM" "\$PROBA/vremennaya/lena-inworld" 2>&1 | head -40
echo ">>RAZNICA"
sudo -n rm -rf "\$PROBA"
if [ -e "\$PROBA" ]; then echo "UBRANO=net"; else echo "UBRANO=da"; fi
REMOTE
)"
KOD="$(echo "$OTVET" | sed -n 's/^KOD=//p')"
STROK="$(echo "$OTVET" | sed -n 's/^STROK=//p')"
MUSOR="$(echo "$OTVET" | sed -n 's/^MUSOR=//p')"
UBRANO="$(echo "$OTVET" | sed -n 's/^UBRANO=//p')"
RAZNICA="$(echo "$OTVET" | sed -n '/^RAZNICA<</,/^>>RAZNICA$/p' | sed '1d;$d')"
ZHDYOM_STROK="$(grep -c '^C ' "$ETALON_PRAVILA")"
if [ "$STROK" = "$ZHDYOM_STROK" ]; then
zelenyy "до опыта доехали все $ZHDYOM_STROK строк правила — опыту есть что делать"
else
krasnyy "до опыта доехало строк ${STROK:-непонятно}, а в эталоне $ZHDYOM_STROK — опыт пустой, судить по нему нельзя"
fi
if [ "$KOD" = "0" ]; then
zelenyy "правило отработало без ошибки — код 0"
else
krasnyy "правило отработало С ОШИБКОЙ — код ${KOD:-непонятно}. Ответ машины целиком:"
echo "$OTVET" | sed 's/^/ | /'
fi
if [ "$MUSOR" = "snesyon" ]; then
zelenyy "мусор из подставного /tmp снесён — стирание идёт РАНЬШЕ создания"
else
krasnyy "мусор из подставного /tmp ОСТАЛСЯ — правило D не отработало, опыт недостоверен"
fi
if [ -z "$RAZNICA" ]; then
zelenyy "все $ZHDYOM заготовок вернулись на подставное дерево один в один"
else
krasnyy "после воссоздания дерево НЕ СОВПАЛО с постоянной копией:"
echo "$RAZNICA" | sed 's/^/ | /'
fi
if [ "$UBRANO" = "da" ]; then
zelenyy "подставное дерево убрано за собой — проверено командой"
else
krasnyy "подставное дерево НЕ убралось: $PROBA_TAM — снести руками"
fi
echo
itog_i_vyyti
+47
View File
@@ -0,0 +1,47 @@
# Слепки заготовок подбора голоса Лены. Снято 06.08.2026 с машины робота
# 51.250.1.97, папка /tmp/lena-inworld. Формат: sha256sum, пути от zagotovki/.
# 🔴 Правит этот файл только тот, кто НАРОЧНО меняет состав заготовок.
# Расхождение с содержимым zagotovki/ — беда, а не повод переписать слепки.
ec9f61a2d87d4e4f166430c93b66037c64273dbc11dd39f29da2f42a2aaa7929 brend/bylo.mp3
78f62e168ef6444a7227e9a480b90ea50bbcba5150ce4f933f4c1c2830e26316 brend/stalo.mp3
71ad42e774ff6a49f348b3886d4d1ae922b5e38d94002bf919c8f9dedbb25c18 chelovek-dorogo.wav
70a352acf364bf6953f735a7c02633bdd9f49fe7e6cc06ac4396d0fd30aaa8af chelovek-nomer.wav
f68098adcfbb593e0e906b66a8e3813160e3c6940ce2f87a62699ee30631ed29 chelovek.syroy
ad7e7c6b819e305fe4eb91241696a848fc4c2e0874816709ec47d2df69c4565e chelovek.wav
a282ddd914cbd4d6b5e1189477a55bd64b5578b20431a87a4eb77ca25201bae5 dyhanie/bez.mp3
d7873e69e9e2f70335eef99c1b7e95dda8ebcf8e7bf9ca500d8d31765156f6b4 dyhanie/vdoh.mp3
2e164d442661857a7b634b2b286e29ed715b64144337369dca7e975395d9e005 itog/bylo.mp3
f45873b0cb27283a61db975bb2cb4abbbdac145e78afc055d7db2dcecf67afc3 itog/stalo.mp3
b4461f24c94173aae29e08793f1985e45a3c61619bab7b524a82fcc12f6d36e7 obraztsy/Alyona.mp3
cc56ab6eb48b39260930fb2b1f1c643aea4b2117efef090573ba8d5fb9b8c6e0 obraztsy/Elena.mp3
1d9798d2c37b7424791c03dad3501d6249682e1376c386559a531652bd0f45fa obraztsy/Galina.mp3
8689c4b1391c5ce4b307e4fdf35d90a630678f318f4b5e152758a58c0ff6c16e obraztsy/Oksana.mp3
2b596c57ef422cbfd37848aab4e45c7df945a432424ee06ac1e26b38db59c4a6 obraztsy/Polina.mp3
c7be4cf4d8ea31f36eed86fcdda9e039617837f9705ec1273318657c9639096b obraztsy/Snezhana.mp3
fbc0f0be30fc975c229fc23deb76559d9260a40afc8b928551d7956d135b9790 obraztsy/Svetlana.mp3
bab00548cff405732aaee45f6018e66612b57a2543ee9b431fda7ce840fdd9a3 obraztsy/Tamara.mp3
23e11596a6dc13f8a080b7fed0d28ade543d5a0e57272b62f4d81b7ac64729b1 obraztsy/Yulia.mp3
843138e9281b992bf30fd2cc9654cb612ba27e139f5d5627c87d5705ab4e8c17 tempy/skorost-0_5.mp3
28d258613c9472ecdae224b816345631b262e0d565b74cbe9f8a96cc42fd3a99 tempy/skorost-0_9.mp3
add37342c4edba513c69f94e7ea8e6515b6d19cbf6451946c3e04a2835e218ab tempy/skorost-1_0.mp3
cfe489b3f7b1f40c6a3124dacf2be25c8eb4b452aaf20a8b6799cd59a577c1ad tempy/skorost-1_1.mp3
c770eb777be67e6a56fc59c44e46a5ed18f0dacbac82a96995d918c05e84d389 tempy/skorost-1_2.mp3
0fbb46068f9baa882631158303ac27bc46783573a14fb543a74a21ac552dbafe tempy/skorost-1_35.mp3
e5bee60d9b174f6264f0dd46106ecdce098158df594fe695a654a42454fc394d tempy/skorost-1_5.mp3
579a5f92dadefa3dfa8db0d27c238cf7ffb5e2a611921915557848efb81db8be tempy2/skorost-0.9.mp3
26df01ed9b5ef0146eb868a969655fe90e8eac72f171433231ceb94f0e043b1d tempy2/skorost-1.0.mp3
c6b3e0b6e14e82350abbb9666c340e502164b4c19f149becb86a13c144f93036 tempy2/skorost-1.1.mp3
f0ed1ee166f8673edf45e4154237be7e4c7be0b923f27c4d0f0337c21477bfa3 tempy2/skorost-1.2.mp3
4ce57cfaf2b47a81fd8c103fca14ea321a92cd6114685943545dba9dbe8a7fca tempy2/skorost-1.35.mp3
dab1386a0d16c5c44a3927be431c3db5720577cea006aed026ff5d4801b0da2c tempy2/skorost-1.5.mp3
2bb27a6bff975f27b9472973c31c31e2c32ca1a1276864a7fcf04a1278de431c udarenie/defis.mp3
538c868152ce0c04d68d55bdf7e283bd01fef44dd8e38ca8f417bb8d8c2d7451 udarenie/prosto.mp3
d63ca5dc24bd2b3b1b6f41e0c8ca54eb644d3d52410199bbd2d1a5f35c27fc34 udarenie/zaglavno.mp3
2545d82691ec422f9d95ffb7728e96b3018f258a1c4b6b653a8cc8a46bbd625f udarenie/znak.mp3
2e99e6e3c148a90e0fd96d666d42622dff6964795847c1d2a2fa724346cc248d zhivost/bez.mp3
c184351ba56f3a96767d205bbf38c5455596f2e48da35758da2e7b4cc095c4bb zhivost/bodro.mp3
f4267fdf24b61926516d0bb374088c768b3c6999ac141c3722069d10fa43f60a zhivost/teplo-vdoh.mp3
d5f1f04ca22a548a9a707989dcc3fb66dd10bb4fc07e1ac9aad8062dfb9450de zhivost/teplo.mp3
f9a61a2bd58e66ef6a858238b9d94ddd45b202a95483322b9c189e213539f960 zhivost/vdoh-nachalo.mp3
98388a6931f749e07d911360e088b9e1bbb2de4d1e58112408206891d2f317f7 zhivost/vdoh-seredina.mp3
7a623305fe7ea44640b4071f0c612857cc884fb373a2a0d722f10baf1271f978 zhivost/zacepka.mp3
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,411 @@
# Отчёт помощника — З-0-9 «Заготовки робота» — 06.08.2026
Ветка: `feat/prospects-manual-testing-kp`. Машина робота: `ubuntu@51.250.1.97` (UTC).
Пишется по ходу работы, а не в конце.
## Разбор задания
Задание разобрано по существу. Картина беды подтвердилась полностью, но **три
вещи в нём нуждаются в поправке**, и одна из них меняет то, что надо написать
человеку.
### Что подтвердилось замером
| утверждение задания | мой замер | сошлось |
|---|---|---|
| папок подбора голоса восемь | `find -maxdepth 1 -type d` → 8 | да |
| файлов `chelovek*` четыре | `find -maxdepth 1 -type f -name 'chelovek*'` → 4 | да |
| записей 86 штук, ~63 МБ | 86 файлов, 65 406 664 байта | да |
| машина не перезагружалась с 05.07 | `uptime` → up 31 days, 20:04 | да |
| `/tmp` вычищается целиком | `D /tmp 1777 root root -` в `/usr/lib/tmpfiles.d/tmp.conf` | да |
| при загрузке зовётся с `--remove` | `ExecStart=systemd-tmpfiles --create --remove --boot --exclude-prefix=/dev` | да |
Число папок и файлов я собрал **заново командой**, а не по числам задания. Оба
перечня сошлись — расхождений нет, находки тут нет.
### Поправка 1 — постоянное место годится, но не по той причине, по какой кажется
Задание предлагает `/home/ubuntu/lena-inworld/zagotovki/`. Путь **годен**, и я его
взял. Но у него есть беда, которой задание не видит: **в этой самой папке уже
работает ежесуточная уборка** `chistka-sleda-prob.sh` (расписание
`/etc/cron.d/lena-chistka-sleda-prob`, 04:50 МСК). Класть заготовки в папку, где
что-то ежедневно удаляется, — само по себе опасно.
Замерил, а не предположил:
```text
105: find "$PAPKA" -maxdepth 1 -type f \
106: -name 'progon-*.txt' \
```
`-maxdepth 1` значит «в подпапки не спускаемся вовсе», `-name 'progon-*.txt'`
имя обязано начинаться на `progon-` и кончаться на `.txt`. Наша подпапка
`zagotovki/` под оба условия не попадает **дважды**. Опасности нет.
🔴 Но вторую беду того же места снять нечем: README этой же папки говорит, что
**`most.py` в ней дважды молча откатился к вчерашней версии, и причина не найдена
до сих пор**. То есть постоянная копия ляжет в папку с доказанной историей
молчаливых подмен. Именно поэтому сторож сверяет машину с хранилищем **побайтово**,
а не «файл есть — и ладно».
### Поправка 2 — «холостой прогон» задания не существует, а настоящее доказательство существует и сильнее
Задание предлагает две замены перезагрузке: опыт на подставном пути и «холостой
прогон на настоящем пути». **Холостого прогона у `systemd-tmpfiles` нет**
`--help` на машине показывает 14 ключей, `--dry-run` среди них отсутствует
(systemd 249). Просить его — значит просить того, чего нельзя сделать.
Зато опыт на подставном пути доказывает **больше**, чем задание надеялось.
Задание говорит: «не доказывает порядок исполнения при настоящей загрузке —
останется допущение, что `D /tmp` стирает раньше, чем наши строки создают».
**Это допущение снято замером.** Подробности — в разделе «Четыре мерки», опыт А:
стирание и воссоздание отданы одному вызову `systemd-tmpfiles --create --remove
--boot` — теми же ключами, что и при загрузке, — и мусор исчез, а заготовки
вернулись. Значит фаза удаления в одном вызове идёт **раньше** фазы создания.
Это не рассуждение, это вывод команды.
### Поправка 3 — молчун заводится, и он хуже, чем в вопросе задания
На вопрос «не завожу ли я нового молчуна» ответ — **да, и молчаний три, а не
одно**. Все три замерены (опыты В, Д, Е, Ж), все три записаны в самом правиле и
в README, все три ловит сторож. Правило `C` лечит **только полное отсутствие**
ровно тот случай, ради которого заводится. Частичную порчу оно не лечит и
**молчит об этом**.
### Что я нашёл сверх задания
**Второе место того же класса — рецепт, а не только изделие.** Заготовки — это
*результат* подбора голоса. *Рецепт* — скрипты, которые их изготовили: они лежат
на машине в `/home/ubuntu/lena-inworld/` и **в хранилище их нет ни одного**:
```text
golos_cheloveka.py obraztsy_golosov.py proba_dyhaniya.py proba_zhivosti.py
tempy_svetlany.py proba_udareniya.py proba_itog.py proba_svyazi.py
kakie_mozgi.py potolok_dliny.py potolok_zhivoy.py ukorachivanie.py
umolchaniya.py progon_mozgov.sh progon_terpeniya.sh progon_nachala_rechi.sh
vygruzka/*.md — пять промтов и баз знаний Лены, 107 КБ
```
Это **не та же беда**: они лежат в `/home`, а не в `/tmp`, и перезагрузку
переживают. Но они существуют ровно в одном месте на свете. Имея их, всё, кроме
`tempy2`, можно было бы изготовить заново — то есть это и есть страховка от
беды, которую задание закрывает копированием изделия. В свой круг я их не тащу
(задание перечислило мои файлы поимённо), но называю прямо: это открытый хвост.
## Разведка
### Что лежит в `/tmp/lena-inworld` — перечень собран командой
- **8 папок**: `brend` (2 файла), `dyhanie` (2), `itog` (2), `obraztsy` (9),
`tempy` (7), `tempy2` (6), `udarenie` (4), `zhivost` (7) — **39 файлов, все `.mp3`**.
- **4 файла `chelovek*`**: `chelovek.wav`, `chelovek-nomer.wav`,
`chelovek-dorogo.wav`, `chelovek.syroy`.
- **86 записей разговоров** — 65 406 664 байта. В хранилище **не едут**.
🔴 Отдельно проверил самое опасное: **внутри восьми папок записей разговоров
нет** — там только `.mp3`, ни одного имени вида `1785…wav` или `vh-…wav`.
Проверено рекурсивным перебором всех 39 файлов, а не на глаз.
### Кто зовёт заготовки
| заготовка | кто зовёт | где |
|---|---|---|
| `chelovek.wav`, `chelovek-nomer.wav`, `chelovek-dorogo.wav` | телефония, раздел `[chelovek-proba]` | `/etc/asterisk/extensions.conf:75,77,79` |
| `chelovek.syroy` | сырьё для `golos_cheloveka.py` | `~/lena-inworld/golos_cheloveka.py:26` |
| `obraztsy/` | `obraztsy_golosov.py:17` | машина |
| `tempy/` | `tempy_svetlany.py:18` | машина |
| `dyhanie/`, `itog/`, `udarenie/`, `zhivost/` | `proba_dyhaniya.py:16`, `proba_itog.py:15`, `proba_udareniya.py:16`, `proba_zhivosti.py:16` | машина |
| `brend/`, `tempy2/` | зовущих не нашёл — изготовителя нет | — |
### Уборка записей заготовки не трогает
`chistka-zapisey.sh` берёт `find -maxdepth 1 -type f ! -name 'chelovek*'`
в подпапки не спускается и `chelovek*` не трогает по имени. Значит воссозданные
заготовки суточная уборка не съест. Проверено чтением скрипта, строки 104–106.
## Что сделал
### В хранилище
| файл | что это |
|---|---|
| `bots/lena-golos/zagotovki/**` | 43 файла заготовок, 4 773 060 байт |
| `bots/lena-golos/zagotovki-slepki.sha256` | слепок каждого из 43 файлов |
| `bots/lena-golos/lena-zagotovki.conf` | эталон правила воссоздания, 12 строк `C` |
| `bots/lena-golos/proverka-zagotovok.sh` | сторож, 21 проверка |
| `bots/lena-golos/README.md` | дописан раздел «Заготовки подбора голоса» |
Перенос — одной свёрткой `tar` с **поимённым** перечнем из 12 имён верхнего
яруса. Не «всё, кроме записей», а «только вот это». Свёртка перед распаковкой
проверена двумя воротами: ни одного члена с именем записи разговора, ни одного
имени верхнего яруса сверх разрешённых 12.
### На машине
| что | где |
|---|---|
| постоянная копия | `/home/ubuntu/lena-inworld/zagotovki/` — 43 файла, 4,7 МБ |
| правило воссоздания | `/etc/tmpfiles.d/lena-zagotovki.conf` — 8055 байт, 12 строк `C` |
Постоянная копия сделана `cp -a` **из `/tmp` на самой машине**, тем же поимённым
перечнем — значит она побайтово равна и живой папке, и хранилищу. Проверено:
все 43 слепка сошлись по всем трём сторонам.
Правило доставлено через времянку с явно временным именем и снесено за собой:
`времянки снесены: 0 осталось`.
🔴 На машине я не удалил ничего, кроме своих времянок, и каждое удаление
проверил командой. Машину не перезагружал.
### Почему правило устроено 12 строками, а не одной
Замер (опыт В): строка `C` на папку копирует, только если цель **не существует
или пуста**. Одна общая строка на `/tmp/lena-inworld` была бы пропущена целиком
и молча, если бы папку успел создать кто-то другой. Двенадцать отдельных строк
от этого не зависят.
🔑 **А кто мог бы успеть — измерено, и ответ «никто».**
`systemd-tmpfiles-setup.service` объявлен `Before=sysinit.target`, а Asterisk —
`After=network-online.target -.mount sysinit.target system.slice basic.target`.
То есть systemd **гарантирует** порядок: заготовки возвращаются раньше, чем
телефония вообще стартует. Гонки нет. Но 12 строк оставлены — они дешевле
и не зависят от того, что кто-нибудь однажды поменяет порядок служб.
## Ножи — сторож показан красным
Сторож не считается сторожем, пока его не видели красным. Показан **шесть раз**,
каждый раз на своём случае. Все ножи — **в хранилище**, машину я не ломал.
### Нож 0 — естественный, до установки
Первый же прогон сторожа был сделан **до** того, как что-либо легло на машину.
```text
🔴 постоянной копии на машине НЕТ: /home/ubuntu/lena-inworld/zagotovki — после перезагрузки возвращать будет НЕЧЕГО
🔴 правила на машине НЕТ: /etc/tmpfiles.d/lena-zagotovki.conf — после перезагрузки заготовки не вернутся
🔴 systemd РУГАЕТСЯ на правило: Failed to open '/etc/tmpfiles.d/lena-zagotovki.conf': No such file or directory
🔴 БЕДА: проверок 20, красных 5
```
Это и есть четвёртый случай второго рода из задания — «в хранилище всё, а
постоянного пути на машине нет вовсе». Сторож его назвал.
### 🔴 И этот же прогон поймал ДВА ЛОЖНЫХ ЗЕЛЁНЫХ в самом стороже
Пока сторож был красным, стало видно то, чего в зелёном не увидишь никогда:
1. `✅ все 43 заготовок на машине сходятся с хранилищем побайтово` — **при
том, что папки на машине не было ВООБЩЕ.** Причина: `cd` в несуществующую
папку обрывал команду, ответ приходил пустым, а пустой ответ значил «всё
сошлось».
2. `✅ правило отработало без ошибки — код 0` в живом опыте — **при пустом
правиле.** `systemd-tmpfiles` на пустом наборе строк честно возвращает 0:
«ничего не делать» выглядело как «всё получилось».
Оба вылечены одинаково: машина обязана в конце сказать слово `###ZAMERENO`, а
опыт обязан сообщить, сколько строк правила до него доехало, и это число
сверяется с эталоном. Без этих слов пустой ответ теперь красный, а не зелёный.
После починки тот же прогон дал **7 красных вместо 5** — две проверки перестали
врать.
### Ножи 1–5 — по одному на допущение
| нож | что ломал | что сказал сторож | возврат доказан |
|---|---|---|---|
| 1 | убрал ОДИН файл внутри каталога — `zhivost/zacepka.mp3` | `файлов 42, а слепков 43` + `zhivost/zacepka.mp3: FAILED open or read` | слепок `7a623305…f978` до и после — совпал |
| 2 | обнулил файл до 0 байт — `obraztsy/Svetlana.mp3` | `obraztsy/Svetlana.mp3: FAILED`**имя есть, содержимое не то** | слепок `fbc0f0be…9790` до и после — совпал |
| 3 | положил в заготовки файл с именем записи разговора | `🔴 СРЕДИ ЗАГОТОВОК ЗАПИСЬ РАЗГОВОРА — ЭТО ЛИЧНЫЕ ДАННЫЕ: 1785699262.18.wav` + `файлы БЕЗ слепка` | подставная снесена, проверено командой |
| 4 | завёл на машине девятую папку `proba-devyatoy-papki-storozha-06-08` | `на машине ЕСТЬ заготовки, которых НЕТ в хранилище` — назвал по имени | снесена, верхний ярус снова 8 папок |
| 5 | вычеркнул из правила строку про `tempy2` | `правило и заготовки РАЗОШЛИСЬ``> tempy2` | слепок правила `d1809f8d…4c3e` до и после — совпал; на машине тот же |
🪤 Нож 5 дал побочную находку: `git checkout --` **не вернул** файл, потому что
он ещё не был в истории. Восстанавливал строкой и доказывал слепком, а не
памятью. Ровно та ловушка, от которой требование «доказывать слепком» и заведено.
## Четыре мерки
### Мерка 1 — сторож показан красным
Показан шесть раз (см. раздел выше). Ронял: удалением файла внутри каталога,
обнулением файла до нуля байт, подкладыванием файла с именем записи разговора,
заведением девятой папки на машине, вычёркиванием строки из правила, и
естественным состоянием «на машине ещё ничего нет». Каждый раз сторож называл
**что именно** сломано, а не просто краснел. Возврат каждый раз доказан
`sha256sum`, а не памятью.
Итоговое состояние сторожа:
```text
✅ ЦЕЛО: проверок 21, красных 0
```
### Мерка 2 — полный прогон
```text
cd app && DB_DATABASE=liderra_testing_stroyka5 php artisan test --filter=Obzvon
{"tool":"pest","result":"passed","tests":214,"passed":214,"assertions":958,"duration_ms":73562}
```
**214 из 214, 958 утверждений, красных 0** — точка отсчёта смены удержана
один в один.
Три сторожа машины робота:
| сторож | было | стало | красных |
|---|---|---|---|
| `proverka-raspisaniya.sh` | 26 | **26** | 0 |
| `proverka-tekstovogo-sleda.sh` | 46 | **46** | 0 |
| `proverka-chistki.sh` | 27 | **27** | 0 |
Ни у одного число проверок не уменьшилось.
### Мерка 3 — две строки про соседей
**моего имени нет ни в одном выводе ни одного сторожа** — замерено: поиск по
`zagotovk|proverka-zagotovok|lena-zagotovki|slepki` в выводах всех трёх чужих
сторожей дал 0, 0 и 0 упоминаний.
**чужие файлы в дереве не тронуты** — я писал ровно в пять путей:
`bots/lena-golos/zagotovki/**`, `bots/lena-golos/zagotovki-slepki.sha256`,
`bots/lena-golos/lena-zagotovki.conf`, `bots/lena-golos/proverka-zagotovok.sh`,
`bots/lena-golos/README.md` и свой отчёт. Ни один файл из запрещённого списка и
ни один файл двух соседних смен не открывался на запись. Коммит собран
поимённо, `git add .` не применялся ни разу.
### Мерка 4 — зовущие поимённо
Искал глазами по всему дереву, а не числом.
| моё | кто зовёт | сколько |
|---|---|---|
| `proverka-zagotovok.sh` | **никто** — ни расписания, ни крючка, ни сборки. Упоминается только в README как «запусти руками», в плане смены и в приёмочном листе надзирателя | **0 автоматических** |
| `lena-zagotovki.conf` | `systemd-tmpfiles-setup.service` при **каждой загрузке машины** — но по имени его не зовёт никто: служба берёт **всю папку** `/etc/tmpfiles.d/` целиком. Плюс сторож, строки 42 и 50 | 1 автоматический + 1 сторож |
| `zagotovki-slepki.sha256` | сторож, строка 41; и человек руками по README | 1 + руками |
| `zagotovki/**` | сторож; на машине — правило через постоянную копию | 2 |
🔑 **Улов ловушки счётчика.** Заготовки `chelovek*` зовёт телефония, и найти это
поиском по имени файла **невозможно**: в настройке стоит
```text
exten => 1,1,Set(FRAZA=/tmp/lena-inworld/chelovek)
```
— то есть, во-первых, **без расширения `.wav`** (его подставляет сам Asterisk),
а во-вторых, дальше зовётся не имя, а **переменная `${FRAZA}`**. Поиск по
`chelovek.wav` дал бы честный ноль при живом зовущем. Две ловушки из
перечисленных восьми — «переименование на входе» и «зов внутри `${…}`» —
сработали здесь одновременно.
🔴 **У моего сторожа зовущих ноль, и это находка, а не провал.** Он повторяет
судьбу двух соседних сторожей: `proverka-raspisaniya.sh` и
`proverka-tekstovogo-sleda.sh` тоже никем не зовутся, и README прямо называет
это открытым хвостом. Пока сторожа не зовут, защита от молчаливой пропажи сама
молчалива.
### Опыт А — порядок «сначала стереть, потом создать» ДОКАЗАН
Подставное дерево `/tmp/lena-proba-vosstanovleniya-06-08`, целиком от `root`
(иначе `systemd-tmpfiles` отказывается: «Detected unsafe path transition»).
Три файла правил — подставные близнецы настоящих: `D` на подставной `/tmp`,
`d` на папку Лены, `C` на две заготовки.
```text
=== ДО ===
.../vremennaya/musor.txt
.../vremennaya/lena-inworld/1785699262.18.wav
sudo systemd-tmpfiles --create --remove --boot aa-tmp.conf bb-lena-inworld.conf cc-lena-zagotovki.conf
код возврата 0
=== ПОСЛЕ ===
d ubuntu .../vremennaya/lena-inworld
f root .../vremennaya/lena-inworld/chelovek.wav ← вернулась
d root .../vremennaya/lena-inworld/brend
f root .../vremennaya/lena-inworld/brend/bylo.mp3 ← вернулась
musor.txt — исчез
1785699262.18.wav — исчез
```
Ключи — **те же самые**, какими зовёт загрузка (`systemctl cat
systemd-tmpfiles-setup.service``ExecStart=systemd-tmpfiles --create --remove
--boot --exclude-prefix=/dev`). Мусор снесён, заготовки возвращены, всё в одном
вызове. **Допущение о порядке снято.**
### Опыты Б–Ж — чего правило НЕ умеет (вырезание второго рода)
| опыт | случай | что вышло |
|---|---|---|
| Б | имя файла правил сортируется РАНЬШЕ `lena-inworld.conf` | заготовки вернулись всё равно — **порядок имён не важен** |
| В | папка `brend` есть и НЕ пуста (чужой файл) | 🔴 копирование **молча пропущено целиком** |
| Г | папка `brend` есть и ПУСТА | заготовки вернулись |
| Д | папка `brend` есть, внутри 1 файл из 2 | 🔴 второй **не вернулся**, ругани нет |
| Е | `chelovek.wav` есть, но 0 байт | 🔴 **не перезаписан**, так и остался пустым |
| Ж | источника нет вовсе | ругань `Failed to copy files…`, код возврата 73 |
Пустого улова тут нет: защита **не выдержала** три случая из шести.
### Вырезание второго рода — четыре случая задания поимённо
Допущение, на котором стоит вся защита: **«перезагрузка стирает ВСЁ, значит все
цели исчезают целиком»**. Правило `C` умеет ровно это и ничего больше. Ниже —
случаи, которые в допущение не попадают.
| случай из задания | лечение заметит? | сторож заметит? | доказано |
|---|---|---|---|
| пропал не каталог, а ОДИН ФАЙЛ внутри | 🔴 **нет**`C` пропустит каталог целиком | ✅ да, назвал по имени | опыт Д + нож 1 |
| на машине девятая папка, в хранилище её нет | 🔴 **нет** — правило про неё не знает | ✅ да, назвал по имени | нож 4 |
| файл на месте, но пустой, 0 байт | 🔴 **нет**`C` не перезаписывает | ✅ да, по слепку | опыт Е + нож 2 |
| в хранилище всё, а на машине пути нет вовсе | 🔴 при загрузке будет ругань в журнал, который никто не читает | ✅ да, три красных сразу | опыт Ж + нож 0 |
**Пустого улова нет ни в одном из четырёх.** Лечение слепо ко всем четырём;
сторож видит все четыре. То есть защита держится **не на правиле, а на стороже**
— а сторожа никто не зовёт (мерка 4). Это и есть главное незакрытое место.
### Мои случаи сверх четырёх заданных
| случай | что вышло |
|---|---|
| файл в заготовках с именем записи разговора (утечка ПДн в обратную сторону) | ✅ сторож ловит, ворота ПДн — нож 3 |
| завели заготовку, а строку в правило дописать забыли | ✅ сторож ловит — нож 5 |
| порядок чтения файлов правил другой | ✅ защита **выдержала** — опыт Б, пустой улов |
| правило доехало пустым, а systemd вернул 0 | 🔴 сторож врал зелёным, **починено** — см. ножи |
| папки на машине нет, а сверка отвечает «сходится» | 🔴 сторож врал зелёным, **починено** — см. ножи |
## Что осталось незакрытым
1. 🔴 **Сторожа никто не зовёт — ноль автоматических зовущих.** Ни расписания,
ни крючка. Пока его не запускают, всё, что он умеет ловить, он будет ловить
только тогда, когда кто-нибудь вспомнит. Ровно тот же открытый хвост, что у
двух соседних сторожей. Своей волей расписание я не заводил: это правка
боевой машины сверх задания, и решать её владельцу.
2. 🔴 **Правило лечит только полное отсутствие.** Частичная порча (пропал файл
внутри каталога, файл обнулился) не лечится и не сообщается. Замерено,
записано в самом правиле и в README. Чинить это правилом `tmpfiles` нечем —
у него нет режима «досыпать недостающее».
3. **Настоящая перезагрузка не проверялась и не могла** — на машине живёт
рендер боевого «Поиска клиентов». Доказано вместо этого: порядок фаз внутри
одного вызова теми же ключами (опыт А) и порядок служб по объявлениям
systemd (`tmpfiles-setup``Before=sysinit.target`, Asterisk —
`After=sysinit.target`). **Чего это не доказывает:** что при настоящей
загрузке служба `systemd-tmpfiles-setup` вообще отработает успешно —
сегодня она `Result=success`, но это про прошлую загрузку, месяц назад,
когда нашего правила ещё не было.
4. 🔴 **Рецепт не сохранён — только изделие.** 16 скриптов и 5 промтов в
`~/lena-inworld/` и `~/lena-inworld/vygruzka/` существуют ровно в одном
месте на свете. Перезагрузку они переживают (лежат в `/home`), поэтому это
не та беда, что закрывалась сегодня, — но потеря диска унесёт их целиком.
В свой круг не тащил: задание перечислило мои файлы поимённо.
5. **`cspell` физически не видит `docs/superpowers/**`** — это в `ignorePaths`
самого `cspell.json`. Прогон по моему отчёту честно отвечает
`Files checked: 0`, и это выглядит успехом. Правописание отчёта проверено
обходом — временной копией в корне хранилища, которая проверяется; копия
снесена, снос проверен командой. Предупреждение задания было про путь вне
хранилища; у той же беды есть **вторая причина**, и она сильнее.
6. **Не проверял**, что будет, если `/home` окажется недоступен в момент
загрузки (отдельный раздел, сбой диска). По опыту Ж ждать надо ругани в
журнал и заготовок не будет — но замера на этом случае я не делал.
<!-- Имена файлов и папок на сервере записаны транслитом, живут только здесь. -->
<!-- 🪤 Слова обязаны стоять на ОДНОЙ строке с cspell:ignore — указание читается
до конца строки, перенесённые вниз слова сами становятся ошибками. -->
<!-- cspell:ignore brend chelovek cheloveka chistka chistki devyatoy dorogo dyhanie dyhaniya FRAZA golos golosov nomer obraztsy papki proba progon proverka raspisaniya sleda slepki storozha Svetlana svetlany syroy sysinit tekstovogo udarenie udareniya vygruzka vosstanovleniya zacepka zagotovk zagotovki zagotovok ZAMERENO zapisey zhivost zhivosti -->