fix(ночь): прибор объявлял мёртвым сторожа, которого поднимает сам прогон

Разбор 33 «точных долгов» глазами (работа 2 из промта смены 26). Настоящих
оказалось 20, ложных тревог 13 — почти сорок процентов. Одна ложная тревога была
ОПАСНОЙ.

`setup` и `teardown` из `storozh-boevogo-hozyaystva.mjs` шли в приговоре с самой
твёрдой пометкой прибора — «не зовёт никто вовсе, даже проверки». У этой пометки,
в отличие от «зовут только проверки», нет оговорки «посмотри глазами». А зовёт их САМ
ПРОГОН: файл назван в его настройке, `app/vitest.config.tools.mjs` строка 56,
`globalSetup`. Зова в коде нет и быть не может — соглашение об имени скобками
не выглядит никогда.

То есть прибор, написанный против класса «написано, но не работает», подталкивал
снести работающее. Прочти следующая смена этот приговор буквально — и пошла бы
вырезать сторожа боевого хозяйства как мёртвый код.

РЕЗ УЗКИЙ НАРОЧНО, и это главное в правке. Наружу из затеи торчат три дороги,
зовом имени считается ОДНА:
 - `globalSetup` — прогон зовёт `setup`/`teardown` сам по соглашению — засчитываем;
 - `setupFiles` — файл просто исполняется сверху вниз — имён не зовёт;
 - наряд `package.json` (`node tools/night/utro.mjs`) — имён не зовёт, там рот.
Широкий рез тут не ослепил бы прибор, а ЗАТКНУЛ: засчитай мы наряды — все имена
`cli.mjs` и `utro.mjs` вышли бы из долга разом, и живая находка `merkaCheloveka`
пропала бы из приговора молча.

ДОКАЗАНО ВЫРЕЗАНИЕМ, ДВАЖДЫ. Убрал встройку в обход — покраснела ровно одна,
сквозная (число красных посчитано ДО прогона, сошлось). Расширил рез на `setupFiles` —
покраснел встречный сторож послаблений.

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

Номера новому пятну не даю: нумерация в шапке прибора сама себе противоречит — одно
и то же пятно (ввоз модуля целиком) названо там и «шестым», и «№9». Имя вместо номера.

Разбор всех 33 поимённо — docs/superpowers/zamer/2026-08-18-razbor-33-dolgov.md.
Оттуда же два новых долга, ни один не начат:
 - механизм вопросов владельцу мёртв С ОБЕИХ СТОРОН: команд `cli.mjs answer` и
   `cli.mjs ack`, которыми примечание обещает класть ответ, в `cli.mjs` нет вовсе;
 - живая сверка настроек не спрашивает `kudaOtnesyon` — замерено подложным ключом:
   выдуманный ключ прошёл молча, а правило при этом честно ответило «не отнесён никуда».

Приговор прибора: 33 -> 31 точных долга, 610 вывозится.
Проверок 2916 в 106 файлах, ноль красных, код 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-08-06 22:43:08 +03:00
parent 9da4d13903
commit 5b0a7da854
4 changed files with 351 additions and 11 deletions
@@ -0,0 +1,154 @@
# Разбор 33 долгов прибора «кто зовёт это в жизни» — глазами, по одному
**Смена 26, 06.08.2026.** Работа №2 из промта смены 26: «Прибор их называет за 21 секунду,
но приговор он выносит по устройству, а не по замыслу. Правило чтения: спроси у каждого
„а для кого он писался?“».
> 🔴 **ГЛАВНОЕ ЧИСЛО.** Из 33 «точных долгов» настоящих оказалось **20**, ложных тревог —
> **13, то есть почти сорок процентов**. И одна ложная тревога была **опасной**: прибор
> объявил мёртвым сторожа, который поднимается на каждом прогоне.
---
## 🔴🔴 Опасная ложная тревога: прибор подталкивал снести живое
`setup` и `teardown` из `storozh-boevogo-hozyaystva.mjs` шли в приговоре с самой твёрдой
пометкой прибора — **«не зовёт никто вовсе, даже проверки»**. У этой пометки, в отличие
от «зовут только проверки», нет оговорки «посмотри глазами».
А зовёт их **сам прогон**, потому что файл назван в его настройке:
```
app/vitest.config.tools.mjs:56 → globalSetup: ['../tools/night/storozh-boevogo-hozyaystva.mjs']
```
Зова в коде нет и быть не может: соглашение об имени скобками не выглядит никогда.
🔑 **Прибор против класса «написано, но не работает» сам подталкивал снести работающее.**
Прочти следующая смена этот приговор буквально — и пошла бы вырезать сторожа боевого
хозяйства как мёртвый код.
**Починено этой же сменой** (см. ниже «Что сделано»). Приговор упал 33 → 31.
---
## Разбор по одному
### 🟢 Ложные тревоги — 13
| имя | файл | почему не долг |
|---|---|---|
| `setup`, `teardown` | `storozh-boevogo-hozyaystva.mjs` | 🔴 поднимает **сам прогон** через `globalSetup` |
| `schyotDolga`, `strokaDolga`, `perebrat`, `svezhiePoddelki`, `otkazPoddelki` | `lezhachie-sledy.mjs` | **тело сторожа есть сама проверка**: вторая половина сторожа течи, заведена 04.08 по прямому решению владельца; краснеет на полном прогоне |
| `PREDEL_ZHIVOGO_ZAPUSKA`, `PREDEL_SAMOY_PROGRAMME` | `predel-zhivogo-zapuska.mjs` | писались **для проверок**, их и должны звать только они (названо ещё в промте) |
| `readEvents` | `heartbeat.mjs` | работа делается **напрямую**: события читают `sudya-nochi.mjs:146` и `zhiv-li-progon.mjs:113` своим путём |
| `vorotaVetkiNochi` | `vetka-nochi-ne-otstayot.mjs` | лишняя обёртка над половинками, которые зовут напрямую (названо в промте) |
| `schyotZovushchih` | `schyot-zovushchih.mjs` | счёт по одному имени; обход зовущих ходит своей дорогой |
| `readProbeAttempts`, `readSpawnAttempts` | `schyot-zamerov.mjs` | читалки журналов для проверок; живой счёт замеров идёт своим путём |
🪤 **Четыре из них — один и тот же новый род:** «работа делается, но мимо обёртки».
Две дороги к одному файлу могут разъехаться молча — это мелкий долг, но не мёртвый код.
### 🔴 Настоящие долги — 20
**А. Механизм «спросить владельца и принять ответ» — 11 имён, мёртв С ОБЕИХ СТОРОН**
| имя | файл |
|---|---|
| `askOwner`, `mustAsk`, `answerQuestion`, `applyAnswer`, `assumptionOpen`, `deadlineHit`, `otvetDnyom`, `priznakiPunkta`, `prostoyLiVpravdu` | `questions.mjs` |
| `recordOwnerAck`, `OWNER_ACTION_KINDS` | `mailer.mjs` |
🔴 **Смена 25 нашла половину.** Вторая половина хуже: примечание в `mailer.mjs` говорит,
что действия владельца «пишет ПОМОЩНИК теми же командами, что кладут ответ: `cli.mjs answer`
и `cli.mjs ack`». **Таких команд в `cli.mjs` нет вовсе** — прибор 4 называет семь, и этих
двух среди них нет (замерено).
⇒ Владельцу не только нечем задать вопрос — **ему некуда положить ответ**.
**Б. Долги, названные вслух там, где их надо подключить — 2**
| имя | где сказано |
|---|---|
| `zadachaKonchena` | `supervisor.mjs:2239`: «ДОЛГ КУСКА 7 (позиция 7): **ИМЕННО ЭТА СТРОКА** заменяется на зов». Вместо правила стоит упрощение `otlozhennyePunkty.size === 0` |
| `reshyonnyeSNevypolnimymi` | `items.mjs`: «правило готово, а живой провод к нему — нет: кто будет его делать, обязан сперва завести ленджер невыполнимых» |
🪤 **Но примечание в `items.mjs` при этом врёт** — про `zadachaKonchena` там сказано
«**зовут его двое**: блок 5а куска 4 и блок 6а куска 7». Замерено командой: живого зова нет
ни одного. Это правило 235 в чистом виде — задуманное записано в настоящем времени.
**В. Три находки смены 25 — 4 имени**
| имя | что это |
|---|---|
| `mayTouchFakt` (`file-claims.mjs`) | 🔴 защита «файл не заявлен — значит ничей»; **ждёт слова владельца** |
| `merkaCheloveka`, `merkaAvariynoy` (`utro.mjs`) | 🟡 мерка человечности; 🆕 **мерок ДВЕ, а не одна** — вторая для аварийной бумаги |
**Г. Новый долг, найденный этой сменой — 1**
| имя | что это |
|---|---|
| `kudaOtnesyon` (`sverka-nastroek-s-resheniyami.mjs`) | 🔴 «ключ живой настройки не отнесён никуда — и это беда» |
🔴 **Замерено, а не предположено.** Подложил в настройку выдуманный ключ и позвал живую сверку:
```
Сверка сказала про выдуманный ключ: НЕТ — прошёл молча
А само правило говорит: null
```
⇒ Правило готово и верно отвечает «не отнесён никуда». Живая сверка его не спрашивает.
Заведи кто-нибудь новый ключ в настройках ночи — он пройдёт мимо владельца молча.
**Д. Прочее — 2**
| имя | что это |
|---|---|
| `mailFailedSince` (`mailer.mjs`) | «не ушла ли сводка с такого-то часа» — не зовёт никто |
| `readResult` (`spawn.mjs`) | разбор последней строки вывода работника — не зовёт никто |
---
## 🔑 Чему научил разбор
**237. У ПРИБОРА, СУДЯЩЕГО «КТО ЭТО ЗОВЁТ», СПРОСИ СНАЧАЛА — А ВСЕ ЛИ ДОРОГИ ЗОВА ОН ЗНАЕТ.**
Зов приходит не только из кода: из настройки прогона (`globalSetup`), из наряда, по соглашению
об имени. Прибор, знающий одну дорогу, объявляет мёртвым живое — и приговор его тем твёрже,
чем меньше дорог он знает.
**238. СТОРОЖ, ЧЬЁ ТЕЛО ЕСТЬ САМА ПРОВЕРКА, ЖИВ — ЕСЛИ ПРОВЕРКИ ГОНЯЮТ.** Пять имён
`lezhachie-sledy.mjs` не зовёт ни один рабочий файл, и это правильно: сторож течи работает
тем, что краснеет на полном прогоне. Вопрос «для кого писался» тут отвечается не «для
проверок», а «**чем он доставляется**».
**239. 🪤 РАБОТА, ИДУЩАЯ МИМО СВОЕЙ ЖЕ ОБЁРТКИ, — НЕ МЁРТВЫЙ КОД, А РАЗЪЕЗД.** Четыре
случая из тринадцати: обёртка есть, а зовут напрямую. Сносить нельзя, оставлять как есть —
значит держать две дороги к одному месту, которые однажды разойдутся молча.
---
## Что сделано этой сменой
🟢 **Слепое пятно «зов из настройки» закрыто.** Прибор читает `globalSetup` настройки
прогона и не сажает `setup`/`teardown` такого файла в точный долг. Рез **узкий нарочно**:
| дорога наружу | считается зовом имени? |
|---|---|
| `globalSetup` — прогон зовёт `setup`/`teardown` сам | ✅ да |
| `setupFiles` — файл исполняется сверху вниз | ❌ нет, имён не зовёт |
| наряд `package.json` (`node tools/night/utro.mjs`) | ❌ нет, там рот |
🔴 **Почему рез узкий — это главное.** Широкий рез здесь не ослепил бы прибор, а **заткнул**:
засчитай мы наряды — все имена `cli.mjs` и `utro.mjs` вышли бы из долга разом, и живая
находка `merkaCheloveka` пропала бы из приговора молча.
**Доказано вырезанием, дважды:**
1. убрал встройку в обход → покраснела ровно одна, сквозная (**считал заранее — сошлось**);
2. расширил рез на `setupFiles` → покраснел встречный сторож послаблений.
**Голый шов закрыт нарочно:** правило умеет читать настройку, обход умеет судить — и обе
половины были бы зелены, даже если обход правила не спрашивает вовсе. Ловится только
сквозной проверкой на подложном уголке.
**Числа:** проверок 2916 в 106 файлах (было 2911), ноль красных, код 0.
Приговор прибора: **610 вывозится, 31 точный долг** (был 33).
@@ -110,6 +110,53 @@ describe('обход зовущих: спрашиваем у всего выво
}
});
it('3Б. 🔴 кого поднимает САМ ПРОГОН — не долг, хоть зова в коде и нет', () => {
// 🔴 БЕДА ЗАМЕРЕНА НА ЖИВОМ (06.08.2026). Обход объявил `setup`/`teardown` сторожа
// боевого хозяйства «не зовёт никто вовсе, даже проверки» — самым твёрдым своим
// приговором, у которого нет оговорки «посмотри глазами». А их зовёт сам прогон:
// файл назван в его настройке (`globalSetup`). Прочти это следующая смена буквально —
// и пошла бы сносить живого сторожа как мёртвый код.
//
// 🪤 ЗАЧЕМ ЗДЕСЬ ОТДЕЛЬНАЯ ПРОВЕРКА, ЕСЛИ САМО ПРАВИЛО УЖЕ ПОКРЫТО (13–16 у соседа).
// Потому что покрыты были бы ДВЕ ЧЕСТНЫЕ ПОЛОВИНКИ, а дыра живёт МЕЖДУ ними: правило
// умеет читать настройку, обход умеет судить — и обе зелены, даже если обход правила
// не спрашивает вовсе. Голый шов ловится только сквозной проверкой.
const dir = mkdtempSync(join(tmpdir(), 'obhod-zovushchih-nastroyka-'));
try {
writeFileSync(join(dir, 'moy-storozh.mjs'), [
'export function setup() { return 1; }',
'export function teardown() { return 2; }',
'export function nikomuNeNuzhnoe() { return 3; }',
].join('\n'), 'utf8');
const nastroyka = join(dir, 'vitest.config.tools.mjs');
writeFileSync(nastroyka, [
'export default {',
' test: {',
" globalSetup: ['../tools/night/moy-storozh.mjs'],",
' },',
'};',
].join('\n'), 'utf8');
const itog = obhodZovushchih(dir, { nastroykaProgona: nastroyka });
const dolgi = itog.tochnyeDolgi.map((v) => v.imya).sort();
// 🔴 Главное: два имени по соглашению вышли из точного долга…
expect(dolgi).not.toContain('setup');
expect(dolgi).not.toContain('teardown');
// …и ушли не в тишину, а под глаза — с пометкой, что их поднимает прогон.
const podnyatye = itog.posmotretGlazami.filter((v) => v.podnimaetProgon).map((v) => v.imya).sort();
expect(podnyatye).toEqual(['setup', 'teardown']);
// 🔴 ВСТРЕЧНЫЙ СТОРОЖ ПОСЛАБЛЕНИЙ, и он тут главнее первого. Прощение выдано ДВУМ
// именам по соглашению, а не всему файлу: сосед по тому же файлу, которого не зовёт
// никто, обязан остаться в долгу. Иначе один `globalSetup` укрыл бы от прибора целый
// файл — и прибор стал бы выгораживать мёртвое, то есть врать в другую сторону.
expect(dolgi).toContain('nikomuNeNuzhnoe');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
it('4. 🔴 пустое место не выдаётся за ноль долгов (правило 233)', () => {
// Каталог без единого рабочего файла — это НЕ «долгов нет», это «считать было нечего».
const dir = mkdtempSync(join(tmpdir(), 'obhod-zovushchih-pusto-'));
+65 -10
View File
@@ -324,13 +324,58 @@ function vvoziatLiModulCelikom(syroy, rabochie, rodnoyFayl) {
return false;
}
// ━━━ СЛЕПОЕ ПЯТНО, НАЙДЕНО 06.08.2026: ЗОВ ИДЁТ ИЗ НАСТРОЙКИ, А НЕ ИЗ КОДА ━━━
//
// 🔴 Прибор смотрит только внутрь затеи — и потому объявил `setup`/`teardown` сторожа
// боевого хозяйства «не зовёт никто вовсе, даже проверки». А зовёт их САМ ПРОГОН, потому
// что файл назван в его настройке: `globalSetup: ['../tools/night/storozh-…mjs']`. Зова
// в коде нет и быть не может — соглашение об имени зовом со скобками не выглядит никогда.
//
// 🔴🔴 Приговор «не зовёт никто вовсе» — самый твёрдый, какой выдаёт прибор: оговорки
// «посмотри глазами» у него нет. Прочти его следующая смена буквально — и пошла бы сносить
// живого сторожа как мёртвый код. Прибор против «написано, но не работает» подталкивал
// снести работающее.
//
// 🔴 РЕЗ УЗКИЙ НАРОЧНО. Наружу из затеи торчат три дороги, зовом имени считается ОДНА:
// · `globalSetup` — прогон зовёт у файла `setup`/`teardown` сам ⇒ засчитываем;
// · `setupFiles` — файл просто исполняется сверху вниз ⇒ имён не зовёт;
// · наряд в `package.json` (`node tools/night/utro.mjs`) ⇒ имён не зовёт, там рот.
// Широкий рез тут не ослепил бы прибор, а ЗАТКНУЛ: засчитай мы наряды — все имена `cli.mjs`
// и `utro.mjs` вышли бы из долга разом, и живая находка `merkaCheloveka` пропала бы молча.
export const IMENA_PO_SOGLASHENIYU = ['setup', 'teardown'];
/**
* Файлы затеи, которые ПОДНИМАЕТ САМ ПРОГОН по своей настройке (`globalSetup`).
* Ответ — набор имён файлов без пути. Настройки нет — пустой набор, а не падение:
* прибор зовут и на подложных уголках во временном месте машины.
* 🔴 Путь доводом, а не приклеенный: иначе правило нечем показать на подложной настройке,
* а сторож, которого не видели красным, не сторожит ничего.
*/
export function faylyPodnimaemyeNastroykoy(putNastroyki) {
let text;
try { text = readFileSync(putNastroyki, 'utf8'); } catch { return new Set(); }
const kusok = /globalSetup\s*:\s*(\[[^\]]*\]|['"`][^'"`]*['"`])/.exec(text);
if (!kusok) return new Set();
const out = new Set();
for (const put of kusok[1].matchAll(/['"`]([^'"`]+)['"`]/g)) {
const imya = put[1].split(/[\\/]/).pop();
if (imya) out.add(imya);
}
return out;
}
// Спросить у каждого вывозимого: кто зовёт тебя в жизни?
//
// 🔴 `byloLiChtoSchitat` стоит здесь не для красоты (правило 233): каталог без единого
// рабочего файла даёт ноль долгов ровно так же, как безупречное хозяйство, и отличить
// их по числу нельзя. Пустое место, посчитанное числом, становится нулём само собой.
export function obhodZovushchih(dir) {
export function obhodZovushchih(dir, { nastroykaProgona = null } = {}) {
const vsyo = vsyoVyvozimoe(dir);
// Кого поднимает сам прогон своей настройкой. Умолчание — настоящая настройка затеи;
// доводом её подменяют в проверках, чтобы показать правило на подложке.
const podnimaemye = faylyPodnimaemyeNastroykoy(
nastroykaProgona ?? join(dir, '..', '..', 'app', 'vitest.config.tools.mjs'),
);
const bezZovushchih = [];
const sZovushchimi = [];
const tochnyeDolgi = [];
@@ -376,9 +421,12 @@ export function obhodZovushchih(dir) {
const vvezeno = vvezenoLiImya(syroy, rabochie, v.imya, v.fayl);
const celikom = vvoziatLiModulCelikom(syroy, rabochie, v.fayl);
const zapis = { ...v, vProverkah, doma, vvezeno, celikom };
// Поднимает ли этот файл сам прогон, и зовётся ли имя у такого файла по соглашению.
const podnimaetProgon = podnimaemye.has(v.fayl) && IMENA_PO_SOGLASHENIYU.includes(v.imya);
const zapis = { ...v, vProverkah, doma, vvezeno, celikom, podnimaetProgon };
bezZovushchih.push(zapis);
if (!vvezeno && !celikom && doma === 0) tochnyeDolgi.push(zapis);
if (!vvezeno && !celikom && doma === 0 && !podnimaetProgon) tochnyeDolgi.push(zapis);
else posmotretGlazami.push(zapis);
}
@@ -420,7 +468,7 @@ export function slovaObhoda(itog) {
if (itog.tochnyeDolgi.length > 0) {
v.push('━━ 🔴 ТОЧНО ДОЛГ: имя никуда не ввезено, дома не зовётся, явных зовов нет ━━');
v.push('Ни один из четырёх обманов прибора тут невозможен — обманываться нечем.');
v.push('Ни один из пяти обманов прибора тут невозможен — обманываться нечем.');
v.push('');
for (const b of itog.tochnyeDolgi) {
const hvost = b.vProverkah > 0
@@ -434,20 +482,27 @@ export function slovaObhoda(itog) {
if (itog.posmotretGlazami.length > 0) {
v.push(`━━ 🟡 ПОСМОТРЕТЬ ГЛАЗАМИ (${itog.posmotretGlazami.length}) ━━`);
v.push('Явных зовов нет, но имя ввезено, либо его модуль ввозят целиком, либо оно');
v.push('зовётся дома — значит прибор мог ослепнуть, и ноль тут ничего не доказывает.');
v.push('зовётся дома, либо файл поднимает сам прогон — значит прибор мог ослепнуть,');
v.push('и ноль тут ничего не доказывает.');
const podnyatye = itog.posmotretGlazami.filter((b) => b.podnimaetProgon);
if (podnyatye.length > 0) {
v.push(`🔴 Из них ${podnyatye.length} поднимает САМ ПРОГОН своей настройкой (globalSetup) —`);
v.push(`зова в коде у них нет и быть не может: ${podnyatye.map((b) => b.imya).join(', ')}.`);
}
v.push('');
}
v.push('━━ 🔴 КАК ЭТО ЧИТАТЬ ━━');
v.push('Ноль зовущих сам по себе — НЕ приговор. Прибор отвечает на вопрос «есть ли явный');
v.push('зов со скобками в чужом рабочем файле», а не на вопрос «подключено ли». Четыре');
v.push('обмана, названные в шапке прибора:');
v.push('зов со скобками в чужом рабочем файле», а не на вопрос «подключено ли». Пять');
v.push('обманов, названных в шапке прибора:');
v.push(' · это постоянная — скобок она не получает никогда, и ноль у неё почти всегда враньё;');
v.push(' · её передали значением (`имя = имя,` в доводах) — скобок тоже нет;');
v.push(' · её зовут в её же файле — родной файл прибор пропускает нарочно;');
v.push(' · модуль ввезли целиком, и зов идёт через точку (`m.имя()`).');
v.push('Сито выше отсеивает все четыре: в «точно долг» попадает лишь то, что никуда');
v.push('не ввезено и дома не зовётся — обмануться там физически нечем.');
v.push(' · модуль ввезли целиком, и зов идёт через точку (`m.имя()`);');
v.push(' · файл поднимает САМ ПРОГОН по своей настройке, и зовёт `setup`/`teardown` без кода.');
v.push('Сито выше отсеивает все пять: в «точно долг» попадает лишь то, что никуда не ввезено,');
v.push('дома не зовётся и прогоном не поднимается — обмануться там физически нечем.');
return v.join('\n');
}
+85 -1
View File
@@ -26,7 +26,10 @@ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { tolkoKod, zovyImeni, schyotZovushchih } from './schyot-zovushchih.mjs';
import {
tolkoKod, zovyImeni, schyotZovushchih,
faylyPodnimaemyeNastroykoy, IMENA_PO_SOGLASHENIYU,
} from './schyot-zovushchih.mjs';
// Короткий заход: очистить образец от примечаний и строк, посчитать зовы имени.
// Ровно то, что делает `schyotZovushchih` над содержимым каждого файла, только без ФС.
@@ -203,3 +206,84 @@ describe('счёт зовущих: семь слепых пятен, назва
expect(skolko("const m = await import('./foo.mjs');\nm.zovi();", 'foo')).toBe(0);
});
});
// ──────────────────────────────────────────────────────────────────────────────
// СЛЕПОЕ ПЯТНО, НАЙДЕНО 06.08.2026 СМЕНОЙ 26: ЗОВ ИДЁТ ИЗ НАСТРОЙКИ, А НЕ ИЗ КОДА
// ──────────────────────────────────────────────────────────────────────────────
// 🔴 БЕДА, ЗАМЕРЕННАЯ ЖИВЬЁМ. Обход объявил `setup` и `teardown` из
// `storozh-boevogo-hozyaystva.mjs` точным долгом со словами «не зовёт никто вовсе, даже
// проверки». А это САМЫЙ ЖИВОЙ сторож затеи: он поднимается на КАЖДОМ полном прогоне,
// потому что назван в настройке прогона:
// app/vitest.config.tools.mjs → globalSetup: ['../tools/night/storozh-boevogo-hozyaystva.mjs']
// Прогон зовёт у такого файла `setup` и `teardown` САМ, ПО СОГЛАШЕНИЮ ОБ ИМЕНИ, — зова
// в коде нет и быть не может. Прибор смотрит только внутрь затеи и настройки не видит вовсе.
//
// 🔴🔴 ЧЕМ ЭТО ОПАСНО, И ЭТО ГЛАВНОЕ. Приговор «не зовёт никто вовсе» — самый твёрдый
// из всех, какие прибор выдаёт: у него нет оговорки «посмотри глазами». Прочитай его
// следующая смена буквально — и пошла бы сносить сторожа боевого хозяйства как мёртвый код.
// То есть прибор против класса «написано, но не работает» сам подталкивал снести работающее.
//
// 🪤 НОМЕРА ЭТОМУ ПЯТНУ НЕ ДАЮ НАРОЧНО. Нумерация в шапке прибора сама себе противоречит:
// одно и то же пятно (ввоз модуля целиком) названо там и «шестым», и «№9». Плодить девятое
// поверх этого значило бы закрепить путаницу. Имя вместо номера: «зов из настройки».
//
// 🔴 РЕЗ УЗКИЙ НАРОЧНО — правило 234 («не режет ли датчик вместе с шелухой зерно»), только
// с другой стороны: слишком широкий рез здесь не ослепит прибор, а ЗАТКНЁТ ЕГО. Из затеи
// наружу торчат ТРИ дороги, и зовом имени считается лишь одна:
// · `globalSetup` — прогон зовёт `setup`/`teardown` по соглашению ⇒ ЗОВ, засчитываем;
// · `setupFiles` — файл просто исполняется сверху вниз ⇒ имён не зовёт;
// · наряд `package.json` (`node tools/night/utro.mjs`) ⇒ имён не зовёт, там рот.
// Засчитай мы вторую и третью — все имена `cli.mjs` и `utro.mjs` разом вышли бы из долга,
// и `merkaCheloveka` (живая находка смены 25) исчезла бы из приговора молча.
describe('счёт зовущих: зов из НАСТРОЙКИ прогона, а не из кода (найдено 06.08.2026)', () => {
function podlozhnayaNastroyka(telo) {
const dir = mkdtempSync(join(tmpdir(), 'nastroyka-progona-'));
const put = join(dir, 'vitest.config.tools.mjs');
writeFileSync(put, telo, 'utf8');
return { dir, put };
}
it('13. файл из globalSetup назван — прогон поднимает его сам', () => {
const { dir, put } = podlozhnayaNastroyka([
'export default {',
' test: {',
" globalSetup: ['../tools/night/storozh-boevogo-hozyaystva.mjs'],",
' },',
'};',
].join('\n'));
try {
const podnimaet = faylyPodnimaemyeNastroykoy(put);
expect(podnimaet.has('storozh-boevogo-hozyaystva.mjs')).toBe(true);
} finally { rmSync(dir, { recursive: true, force: true }); }
});
it('14. ВСТРЕЧНЫЙ СТОРОЖ: setupFiles зовом имён НЕ считается — рез узкий', () => {
// 🔴 Тут и живёт цена ошибки: `setupFiles` исполняет файл целиком, но ни `setup`,
// ни любого другого имени по соглашению не зовёт. Засчитай мы его — прибор начал бы
// выгораживать мёртвое, то есть врать в ту самую сторону, от которой написан.
const { dir, put } = podlozhnayaNastroyka([
'export default {',
' test: {',
" setupFiles: ['../tools/night/nastroyka-storozha-zhivogo-dereva.mjs'],",
' },',
'};',
].join('\n'));
try {
const podnimaet = faylyPodnimaemyeNastroykoy(put);
expect(podnimaet.has('nastroyka-storozha-zhivogo-dereva.mjs')).toBe(false);
expect(podnimaet.size).toBe(0);
} finally { rmSync(dir, { recursive: true, force: true }); }
});
it('15. настройки нет вовсе — пусто, а не падение', () => {
// Прибор обязан работать и там, где настройки прогона нет: его зовут на подложных
// уголках во временном месте машины, и никакой настройки рядом с ними не лежит.
expect(faylyPodnimaemyeNastroykoy(join(tmpdir(), 'takoy-nastroyki-net-12345.mjs')).size).toBe(0);
});
it('16. имена по соглашению названы поимённо и список ЗАКРЫТ', () => {
// 🪤 Список закрыт нарочно: прогон зовёт у globalSetup-файла ровно эти два имени.
// Допиши сюда третье «на всякий случай» — и оно начнёт выгораживать мёртвое.
expect(IMENA_PO_SOGLASHENIYU).toEqual(['setup', 'teardown']);
});
});