fix(ночь): «свободных пунктов нет» больше не значит «задача сделана»; сторож был зелен случайно

Находка М-406, правила 244-246.

Смена 27 передала список «пять доступных настоящих долгов». Правило 243 велит проверять
такой список замером, каждую строку отдельно. Проверил — ДОСТУПНЫМ ОКАЗАЛСЯ ОДИН.
Третья смена подряд с той же долей: 14 ложных из 33 долгов, 3 из 4 разъездов, 4 из 5.

 - `zadachaKonchena` —  настоящий, закрыт этим коммитом;
 - `reshyonnyeSNevypolnimymi` — 🔒 заперт словом владельца: единственный источник
   невыполнимых пунктов — ответ владельца «нет» в `questions.mjs`, а он мёртв целиком;
 - `mailFailedSince` — 🔒 заперт им же: замысел строки 750/1073 называет его дословно
   («затея видит, что сводка не ушла») частью правила ворот «отклик владельца»;
 - `readResult` —  не долг, а МЁРТВЫЙ ОСТАТОК. Дороги «запусти, дождись конца, возьми
   итог» в затее нет: `startWorker` отдаёт живого ребёнка. Четверо читающих потоки делают
   ДРУГОЕ (надзиратель — живьём по строке; `budget` — ищет замеры предела; судья — берёт
   последнее событие `result` и текст приговора; `dengi-bez-zapisi` — первую цену), а расход
   затея нарочно не считает, а спрашивает. Не снесён намеренно: это решение владельца.

ЧТО ПОЧИНЕНО. В блоке 5а надзирателя стояло упрощение `otlozhennyePunkty.size === 0` —
«отложенных нет, значит всё засчитано». Сюда приходят по ДВУМ причинам, и вторая — не
«пунктов не осталось», а «пункт ЕСТЬ, но его файлы держит живой сосед и занять их из-под
руки не вышло». Отложенных при этом нет — и на НЕДОДЕЛАННОЙ задаче поднимался флаг
приёмщику, а владельцу уходило «Пункты задачи кончились и все засчитаны».

Доказано вырезанием: сторож написан первым и покраснел (`items_all_verified` = true на
недоделанной задаче), после починки зелёный, при возврате упрощения — снова красный, и
ровно один, посчитанный ДО прогона. Прибор №6 подтвердил независимо: имя ушло из приговора.

🔴 ГЛАВНОЕ, ЧТО КУПИЛА СМЕНА, И КУПЛЕНО ЧУЖИМ КРАСНЫМ. Полный прогон после починки дал
красное у чужого сторожа `primechanie-ne-rashoditsya-s-pravdoy.test.mjs`. Правило 242 велит
сперва спросить, что он бережёт. Он бережёт согласие примечания с правдой и по замыслу ПРАВ.
Шапку я поправил — он всё равно краснел.

Причина: сторож брал `items.mjs` СЫРЫМ файлом и искал фразу-маркер «НЕТ НИ ОДНОГО», а она
стоит там в ЖИВОМ КОДЕ — в тексте ошибки «в плане задачи нет ни одного пункта». Пока шапка
врала тем же словом, обе половины совпадали и сторож молчал: он был зелёным ПО ДВУМ ПРИЧИНАМ
РАЗОМ, и одна из них случайная.

⇒ Заведён третий рез `tolkoPrimechaniya`, обратный `bezPrimechaniy`, в тот же дом и к той же
развилке «какой брать». Обратность стережётся отдельной проверкой (что вырезал один — то
оставил второй). Все три места сторожа переведены на него, хотя слепота задевала одно:
у двух других фразы-маркера в коде сегодня нет — но это удача, а не защита.

244. У СТОРОЖА, ЧИТАЮЩЕГО ФАЙЛ СЫРЫМ, СПРОСИ: НЕ ОТВЕЧАЕТ ЛИ ЗА ПРИМЕЧАНИЕ ТО, ЧТО
ПРИМЕЧАНИЕМ НЕ ЯВЛЯЕТСЯ. Обратная сторона 234: тот про «рез срезал зерно», этот про
«отсутствие реза подсунуло шелуху».
245. У КРАСНОГО ЧУЖОГО СТОРОЖА ТРИ ИСХОДА, А НЕ ДВА. Третий: прав он по замыслу, но слеп
по способу — тогда чинят САМОГО СТОРОЖА. Спроси, что он измерил, и то ли это, что он
берётся охранять.
246. `grep -i` НА РУССКИХ БУКВАХ РЕГИСТР НЕ ПРИВОДИТ: «НЕТ НИ ОДНОГО» → 0, «нет ни одного»
→ 1, node тем же выражением → нашёл. Мерь тем же телом, каким меряет проверяемый.

ПОПУТНО три примечания приведены к правде (правило 235): у `zadachaKonchena` живой зовущий
теперь ЕСТЬ И ОН ОДИН (второго не будет, пока нет куска 7 — писать «зовут двое» значило бы
чинить враньё половиной правды); `readResult` назван мёртвым остатком; `reshyonnyeSNevypolnimymi`
помечен запертым, а не «некому сделать».

Проверок 2929 в 108 файлах (было 2927), ноль красных, код 0. Прибор №6: вывозится 613,
точных долгов 29 — число то же, состав другой: `zadachaKonchena` ушёл, на его место встал
новый рез (класс правила 231, прибор говорит это сам).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-08-07 06:16:51 +03:00
parent 02e818f055
commit 951b15fb85
8 changed files with 280 additions and 25 deletions
@@ -10828,3 +10828,95 @@ const s = `ушло ${dollary(x)} денег`; → после чистки:
🟢 **Два реза сведены в один дом.** `bezPrimechaniy` перенесён к грубому `tolkoKod`
с развилкой «какой брать». Врозь они и стоили смене 26 сторожа, который физически не мог
покраснеть: искала команду, живущую строкой, а взяла рез, который строки режет.
---
## М-406. Смена 28: сторож был зелёным по ДВУМ причинам разом, и одна из них случайная
**Развилка окупилась ВОСЬМОЙ раз подряд.** Ночи не было; все шесть приборов сошлись знак
в знак с числами смены 27.
### Правило 243 сработало на своём же списке — и опять сняло больше половины
Смена 27 передала список «пять доступных настоящих долгов». Проверил каждую строку
отдельно, как велит свежее правило. **Доступным оказался ОДИН.**
| долг | приговор замера смены 28 |
|---|---|
| `readResult` (`spawn.mjs`) | ❌ **не долг, а мёртвый остаток**. Дороги, которой он писан — «запусти, дождись конца, возьми итог», — в затее нет: `startWorker` отдаёт ЖИВОГО ребёнка. Все четверо читающих потоки делают ДРУГОЕ (надзиратель — живьём по строке; `budget` — ищет замеры предела; судья — берёт последнее событие `result` и текст приговора; `dengi-bez-zapisi` — первую цену). Расход, ради которого он писан, затея **нарочно не ведёт**: не считает, а спрашивает у прибора системы |
| `mailFailedSince` (`mailer.mjs`) | 🔒 **заперт словом владельца.** Замысел, строки 750 и 1073: «затея видит, что сводка не ушла (отправлялка записала свою неудачу) — тут признака жизни мало, нужно ЯВНОЕ слово». Это часть правила ворот «отклик владельца» |
| **`zadachaKonchena` (`items.mjs`)** | ✅ **настоящий, доступный, закрыт этой сменой** |
| `reshyonnyeSNevypolnimymi` (`items.mjs`) | 🔒 **заперт тем же словом.** Единственное место, откуда пункт становится невыполнимым, — ответ владельца «нет» в `questions.mjs`, а он мёртв целиком. Заводить ленджер невыполнимых — значит строить провод к источнику, которого нет |
| `kudaOtnesyon` | ❌ ложная тревога, закрыта сменой 26 |
🔑 **Третья смена подряд с той же долей:** 14 ложных из 33 долгов, 3 из 4 разъездов,
теперь 4 из 5. Списки в этом хозяйстве стабильно наполовину чужие — и стабильно
разбираются замером за час.
### 🟢 Что закрыто: «свободных пунктов нет» ≠ «задача сделана»
В блоке 5а надзирателя стояло упрощение `otlozhennyePunkty.size === 0` — «отложенных нет,
значит всё засчитано». Оно врало в **третьем случае, безнадзорном**.
Сюда приходят по ДВУМ разным причинам:
1. пунктов вправду не осталось — всё закрыто или отложено;
2. пункт **есть**, но его файлы держит живой сосед, и занять их из-под руки не вышло.
Во втором случае отложенных нет — и на **недоделанной задаче** поднимался флаг приёмщику,
а владельцу уходило «Пункты задачи кончились и все засчитаны». Ровно та беда, от которой
предостерегает примечание строкой выше того же файла.
**Доказано вырезанием:** сторож написан первым, покраснел (`items_all_verified` = `true`
на недоделанной задаче), после починки зелёный, при возврате упрощения — снова красный,
и ровно один, посчитанный до прогона. **Прибор №6 подтвердил независимо:** имя ушло
из приговора.
### 🔴🔴 ГЛАВНОЕ, ЧТО КУПИЛА СМЕНА: правило 242 сработало, и ответ оказался ТРЕТЬИМ
После починки полный прогон дал красное **у чужого сторожа**`primechanie-ne-rashoditsya-s-pravdoy.test.mjs`,
проверка 4. Вчерашнее правило 242 велит не откатываться сразу, а спросить: что он бережёт?
Он бережёт согласие примечания с правдой, и по замыслу он **прав**: правило подключили —
шапка обязана это признать. Шапку я поправил. Он всё равно краснел.
**Причина:** сторож брал `items.mjs` **сырым файлом** и искал в нём фразу-маркер
«НЕТ НИ ОДНОГО». А такая фраза стоит там **в живом коде** — в тексте ошибки
`'в плане задачи нет ни одного пункта — считать нечего'`.
⇒ Пока шапка врала тем же словом, **обе половины совпадали, и сторож молчал**. То есть он
был зелёным **по двум причинам разом, и одна из них случайная**. Напиши смена 26 шапку
другими словами — он краснел бы на пустом месте; подключи кто-то правило и поправь шапку
честно — он краснеет, не умея сказать почему. Ровно это и случилось со мной.
**244. 🔑 У СТОРОЖА, ЧИТАЮЩЕГО ФАЙЛ СЫРЫМ, СПРОСИ: НЕ ОТВЕЧАЕТ ЛИ ЗА ПРИМЕЧАНИЕ ТО,
ЧТО ПРИМЕЧАНИЕМ НЕ ЯВЛЯЕТСЯ.** Это обратная сторона правила 234. То говорит: рез может
срезать вместе с шелухой зерно. Это говорит: **отсутствие реза может подсунуть шелуху
вместо зерна.** Ищешь слова файла О СЕБЕ — режь всё, кроме примечаний.
**245. 🔑 У КРАСНОГО ЧУЖОГО СТОРОЖА ТРИ ИСХОДА, А НЕ ДВА.** Смена 27 знала два: «прав он —
откатись» и «прав ты — чини своё». Есть третий, и он встретился первым же: **прав он
по замыслу, но слеп по способу — тогда чинят САМОГО СТОРОЖА.** Отличить просто: спроси,
что именно он измерил, и совпадает ли измеренное с тем, что он берётся охранять. Здесь
он брался охранять слова файла о себе, а мерил весь файл целиком.
**246. 🪤 `grep -i` НА РУССКИХ БУКВАХ РЕГИСТР НЕ ПРИВОДИТ.** Замерено дважды на одном
файле: `grep -c -i "НЕТ НИ ОДНОГО"`**0**, `grep -c -i "нет ни одного"`**1**, `node`
тем же выражением → нашёл. Мой первый замер сказал «фразы в файле нет» — и я чуть не пошёл
искать причину не там. 🔑 **Мерь тем же телом, каким меряет тот, кого проверяешь.**
### Что сделано
🟢 **Долг куска 7 закрыт** — надзиратель зовёт правило вместо упрощения.
🟢 **Слепота сторожа правила 235 закрыта** — заведён третий рез `tolkoPrimechaniya`,
обратный `bezPrimechaniy`, и обратность стережётся отдельной проверкой. Все три места
сторожа переведены на него, хотя слепота задевала одно: у двух других фразы-маркера
в коде сегодня нет — но это **удача, а не защита**.
🟢 **Три примечания приведены к правде** (правило 235): `items.mjs` — «живой зовущий есть,
и он ОДИН» (второго нет и не будет, пока нет куска 7); `spawn.mjs``readResult` назван
мёртвым остатком с поимённым разбором всех четверых, кто делает похожее; `items.mjs`
`reshyonnyeSNevypolnimymi` помечен запертым, а не «некому сделать».
🔴 **`readResult` НЕ снесён намеренно.** Снос необратимее записи, слова владельца на него
нет, а вместе с правилом ушли бы три его проверки. Это отдельное решение владельца,
а не побочный ход чужой работы.
@@ -140,6 +140,24 @@ app/vitest.config.tools.mjs:56 → globalSetup: ['../tools/night/storozh-boevo
---
## 🔴🔴 ПОПРАВКА СМЕНЫ 28 (07.08.2026): «доступных пять» — на деле ОДИН
Смена 27 передала следующей список «осталось доступных пять». Правило 243 велит проверять
такой список замером, каждую строку отдельно. Проверил. **Доступным оказался один.**
| строка списка | приговор замера |
|---|---|
| **`zadachaKonchena`** | ✅ **настоящий и доступный — ЗАКРЫТ сменой 28.** Упрощение `otlozhennyePunkty.size === 0` врало в третьем случае: пункт есть, но его файлы держит живой сосед. Отложенных нет — и на НЕДОДЕЛАННОЙ задаче поднимался флаг приёмщику. Доказано вырезанием, прибор №6 подтвердил независимо |
| `reshyonnyeSNevypolnimymi` | 🔒 **заперт словом владельца, а не «некому сделать»**. Единственный источник невыполнимых пунктов — ответ владельца «нет» в `questions.mjs`, а он мёртв целиком. Ленджер невыполнимых строить некуда: источника нет |
| `mailFailedSince` | 🔒 **заперт словом владельца.** Замысел, строки 750 и 1073: «затея видит, что сводка не ушла — тут признака жизни мало, нужно ЯВНОЕ слово». Это часть правила ворот «отклик владельца», и подключение меняет ночь владельца |
| `readResult` | ❌ **не долг, а мёртвый остаток.** Дороги «запусти, дождись конца, возьми итог» в затее нет: `startWorker` отдаёт живого ребёнка. Четверо читающих потоки делают ДРУГОЕ, разбор поимённо — в примечании над самим правилом. Не снесён намеренно: это решение владельца |
| `kudaOtnesyon` | ❌ ложная тревога, уже закрытая выше (раздел Г) |
🔑 **Третья смена подряд, где список наполовину чужой:** 14 ложных из 33 долгов, 3 из 4
разъездов, 4 из 5 «доступных». Правило проверяют мыслью, список — замером.
---
## 🔑 Чему научил разбор
**237. У ПРИБОРА, СУДЯЩЕГО «КТО ЭТО ЗОВЁТ», СПРОСИ СНАЧАЛА — А ВСЕ ЛИ ДОРОГИ ЗОВА ОН ЗНАЕТ.**
+20 -14
View File
@@ -363,27 +363,33 @@ export function deferredTaskGate({
// ленджера невыполнимых пунктов затея на 02.08 не заводит нигде. Значит правило готово,
// а живой провод к нему — нет: кто будет его делать, обязан сперва завести такой ленджер,
// иначе звать это правило будет нечем.
//
// 🔒 ЗАМЕРЕНО 07.08.2026, СМЕНА 28 — И ЭТО НЕ «НЕКОМУ СДЕЛАТЬ», А «НЕЛЬЗЯ НАЧАТЬ».
// Единственное место затеи, откуда пункт вообще становится невыполнимым, — ответ владельца
// «нет» в `questions.mjs` (`item_marked_impossible`). Механизм вопросов владельцу мёртв
// С ОБЕИХ СТОРОН и ждёт его слова: подключение меняет ЕГО ночь. Значит и это правило
// заперто тем же решением — заводить ленджер невыполнимых, пока их неоткуда взять,
// значит строить провод к источнику, которого нет.
export function reshyonnyeSNevypolnimymi(verified = [], impossible = []) {
return [...new Set([...verified, ...impossible].map(String))];
}
// ── кусок 7, задача 6 ────────────────────────────────────────────────────────────────
// 🔴🔴 ЗОВУЩИХ У ЭТОГО ПРАВИЛА НЕТ НИ ОДНОГО. ЗАМЕРЕНО 06.08.2026, СМЕНА 26.
// 🟢 ЖИВОЙ ЗОВУЩИЙ ЕСТЬ, И ОН ОДИН. ЗАМЕРЕНО 07.08.2026, СМЕНА 28.
//
// 🪤 Ниже стояло — и стоит до сих пор, дочитайте абзац, — «Зовут его двое». Это неправда
// в НАСТОЯЩЕМ времени: живых зовущих ноль, зовут только проверки (11). Оставляю прежний
// текст как есть нарочно, потому что он верно описывает ЗАМЫСЕЛ, — но читать его надо
// как «зваться оно должно двумя», а не «зовётся».
//
// 🟢 А вот в `supervisor.mjs` про то же самое сказано ЧЕСТНО: «ДОЛГ КУСКА 7 (позиция 7):
// ИМЕННО ЭТА СТРОКА заменяется на зов». Будущее время вместо настоящего — и примечание
// из вранья становится наказом. 🔑 Разница между тем и другим тут ровно в одном глаголе.
// Сегодня вместо этого правила там стоит упрощение `otlozhennyePunkty.size === 0`.
//
// 🔴 ЕДИНСТВЕННОЕ место затеи, где записано, что значит «задача кончена». Зовут его двое:
// 🔴 ЧИСЛО ЗДЕСЬ НАЗВАНО ТОЧНО, И ЭТО НЕ ПРИДИРКА. Замысел велит звать правило ДВОИМ:
// блок 5а куска 4 (вслед за засчитанной заявкой) и блок 6а куска 7 (каждый круг).
// Второй дороги к этому правилу не заводить: разъедутся молча, и приёмщик пойдёт снимать
// недоделанную задачу — а владелец утром прочтёт «сделано».
// Подключён ПЕРВЫЙ — `supervisor.mjs`, блок 5а. Второго нет: куска 7 в затее нет вовсе.
// 🪤 Напиши здесь «зовут его двое» — и это снова стало бы правилом 235: задуманное
// в настоящем времени читается как доказательство подключения, будучи его заменой.
// Прежняя смена честно пометила ту же строку как враньё; чинить враньё половиной правды
// нельзя. Пока куска 7 нет — здесь стоит ОДИН.
//
// 🔴 ЕДИНСТВЕННОЕ место затеи, где записано, что значит «задача кончена». Второй дороги
// к этому правилу не заводить: разъедутся молча, и приёмщик пойдёт снимать недоделанную
// задачу — а владелец утром прочтёт «сделано». Ровно эту беду и держал прежний зов:
// вместо правила там стояло упрощение `otlozhennyePunkty.size === 0`, и на пункте,
// чьи файлы держит живой сосед, оно отвечало «всё засчитано» (замерено вырезанием).
//
// 🔴 Третий довод `otlozhennye` обязателен по существу (находка Н8). Отложенный пункт лежит
// в списке `done` очереди — чтобы она не выдала его снова, — но в `items-verified.jsonl`
@@ -50,7 +50,15 @@ const chitat = (imya) => readFileSync(join(NOCH, imya), 'utf8');
// 🆕 Смена 27 перенесла сам рез в `schyot-zovushchih.mjs` — К ГРУБОМУ `tolkoKod`, чтобы оба
// стояли рядом с развилкой «какой брать». Врозь они и стоили смене 26 этой ошибки. Проверка
// 1Б ниже стережёт его по-прежнему, откуда бы он ни ввозился.
import { bezPrimechaniy } from './schyot-zovushchih.mjs';
// 🆕🔴 ВТОРОЙ РЕЗ, И ОН ОБРАТНЫЙ ПЕРВОМУ. ЗАВЕДЁН СМЕНОЙ 28 ПО КРАСНОМУ, КОТОРОЕ ЭТОТ
// СТОРОЖ ВЫДАЛ САМ. Правда о подключении ищется в КОДЕ (`bezPrimechaniy` — режет примечания),
// а слова файла о себе — в ПРИМЕЧАНИЯХ. Пока вторую половину брали сырым файлом, за шапку
// мог ответить живой код: в `items.mjs` фраза-маркер «нет ни одного» стоит в тексте ошибки
// (`'в плане задачи нет ни одного пункта — считать нечего'`), и сторож считал её словами
// шапки. Он был зелёным по ДВУМ причинам разом, и одна — случайная.
// 🔑 Тот же урок, что у первого реза, только с другого конца: у датчика спрашивай не «что
// он режет», а «не отвечает ли за примечание то, что примечанием не является».
import { bezPrimechaniy, tolkoPrimechaniya } from './schyot-zovushchih.mjs';
export { bezPrimechaniy };
@@ -88,11 +96,36 @@ describe('примечание о подключении не расходитс
expect(bezPrimechaniy('/* про questions */ const a = 1;')).toContain('const a = 1');
});
it('1В. 🔴 обратный рез: за примечание не отвечает живой код — иначе сторож зелен случайно', () => {
// 🔴 СВОЯ НАХОДКА СМЕНЫ 28, КУПЛЕННАЯ КРАСНЫМ ЭТОГО ЖЕ СТОРОЖА. Проверка 4 ниже брала
// `items.mjs` СЫРЫМ файлом и искала в нём фразу-маркер «НЕТ НИ ОДНОГО». А такая фраза
// стоит там в живом коде — в тексте ошибки «в плане задачи нет ни одного пункта».
// Значит шапку файла подтверждала строка кода, которая о подключении не говорит НИЧЕГО.
// Пока шапка врала тем же словом, обе половины совпадали и сторож молчал; подключи
// правило и поправь шапку честно — и он краснеет, не умея сказать почему.
expect(tolkoPrimechaniya("const r = { reason: 'в плане нет ни одного пункта' };")).not.toContain('ни одного');
expect(tolkoPrimechaniya('// 🔴 ЗОВУЩИХ НЕТ НИ ОДНОГО')).toContain('НЕТ НИ ОДНОГО');
expect(tolkoPrimechaniya('/* многострочное\n НЕ ПОДКЛЮЧЁН */ const a = 1;')).toContain('НЕ ПОДКЛЮЧЁН');
expect(tolkoPrimechaniya('/* многострочное */ const a = 1;')).not.toContain('const a = 1');
// 🪤 Два реза обязаны быть ОБРАТНЫ друг другу, а не просто разными: что вырезал один,
// то оставил второй. Без этой строки они могли бы разойтись молча, и половина файла
// не попала бы ни к тому, ни к другому.
const obrazec = "// шапка\nconst a = 'тело';";
expect(bezPrimechaniy(obrazec)).toContain('тело');
expect(bezPrimechaniy(obrazec)).not.toContain('шапка');
expect(tolkoPrimechaniya(obrazec)).toContain('шапка');
expect(tolkoPrimechaniya(obrazec)).not.toContain('тело');
});
it('2. 🔴 вопросы владельцу: надзиратель про них молчит — и файл так и говорит', () => {
// 🪤 Тоже по очищенному коду: напиши кто-нибудь в примечании надзирателя слово
// «questions» — и плоский поиск объявил бы механизм подключённым. См. проверку 4.
// 🆕 Вторая половина — обратным резом, как у проверки 4. У `questions.mjs` фразы-маркера
// в живом коде сегодня НЕТ (замерено сменой 28), то есть сторож тут зелен по существу,
// а не по удаче. Рез ставится всё равно: напиши кто-нибудь завтра текст ошибки со словом
// «не подключён» — и эта проверка ослепла бы ровно так же, как ослепла четвёртая.
const podklyucheno = bezPrimechaniy(chitat('supervisor.mjs')).includes('questions');
expect(primechanieSoydyotsya(podklyucheno, chitat('questions.mjs')),
expect(primechanieSoydyotsya(podklyucheno, tolkoPrimechaniya(chitat('questions.mjs'))),
podklyucheno
? 'механизм вопросов ПОДКЛЮЧИЛИ, а примечание всё ещё говорит «не подключён» — поправьте шапку questions.mjs'
: 'примечание в questions.mjs обещает подключение, которого нет',
@@ -104,7 +137,8 @@ describe('примечание о подключении не расходитс
// а режем только примечания — иначе сторож ослепнет насовсем (разбор у `bezPrimechaniy`).
const cli = bezPrimechaniy(chitat('cli.mjs'));
const podklyucheno = /['"`]answer['"`]|['"`]ack['"`]/.test(cli);
expect(primechanieSoydyotsya(podklyucheno, chitat('mailer.mjs')),
// 🆕 Вторая половина — обратным резом (смена 28), по тому же доводу, что у проверок 2 и 4.
expect(primechanieSoydyotsya(podklyucheno, tolkoPrimechaniya(chitat('mailer.mjs'))),
podklyucheno
? 'команды ответа владельца ЗАВЕЛИ, а примечание в mailer.mjs всё ещё говорит «писать нечем»'
: 'примечание в mailer.mjs обещает команды, которых у cli.mjs нет',
@@ -118,8 +152,12 @@ describe('примечание о подключении не расходитс
// за работу — ровно та мина, от которой написан прибор зовущих, и я написал про неё
// строкой выше за минуту до того, как в неё угодил.
// ⇒ Примечания вырезаем ДО поиска, чужим проверенным чистильщиком.
// 🆕🔴 И ВТОРАЯ ПОЛОВИНА — ЧЕРЕЗ ОБРАТНЫЙ РЕЗ (смена 28). Сырой файл сюда подавать
// нельзя: фраза-маркер «нет ни одного» стоит в `items.mjs` в ТЕКСТЕ ОШИБКИ живого кода,
// и пока шапка врала тем же словом, обе половины совпадали случайно. Правду ищем
// в коде, слова файла о себе — в примечаниях; смешивать их и значит ослепнуть.
const podklyucheno = /zadachaKonchena\s*\(/.test(bezPrimechaniy(chitat('supervisor.mjs')));
expect(primechanieSoydyotsya(podklyucheno, chitat('items.mjs')),
expect(primechanieSoydyotsya(podklyucheno, tolkoPrimechaniya(chitat('items.mjs'))),
podklyucheno
? 'правило «задача кончена» ПОДКЛЮЧИЛИ — поправьте шапку items.mjs, там сказано «зовущих нет ни одного»'
: 'примечание в items.mjs говорит о зовущих, которых нет',
+24 -1
View File
@@ -101,7 +101,7 @@ function obrazetsLiTut(vydano) {
return PERED_OBRAZTSOM.test(t) || SLOVA_PERED_OBRAZTSOM.test(t);
}
// ━━━ 🔴🔴 ДВА РЕЗА, И ОНИ НЕ ВЗАИМОЗАМЕНИМЫ. РАЗВИЛКА — ЗДЕСЬ ━━━
// ━━━ 🔴🔴 ТРИ РЕЗА, И ОНИ НЕ ВЗАИМОЗАМЕНИМЫ. РАЗВИЛКА — ЗДЕСЬ ━━━
//
// Стоят рядом НАРОЧНО. Смена 26 обожглась ровно оттого, что они лежали в разных файлах:
// искала команду, живущую строкой (`case 'answer':`), взяла грубый `tolkoKod` — и своими
@@ -111,8 +111,13 @@ function obrazetsLiTut(vydano) {
// |---|---|
// | ИМЯ СО СКОБКОЙ (зов, `имя(`) | `tolkoKod` — грубый: срежет примечания, строки и образцы |
// | СТРОКУ (`'events.jsonl'`, команду, ключ) | `bezPrimechaniy` — режет ТОЛЬКО примечания |
// | ЧТО ФАЙЛ ГОВОРИТ О СЕБЕ (шапку, наказ) | `tolkoPrimechaniya` — обратный второму |
//
// 🔑 Спрашивай у реза не «что он режет», а «не режет ли он вместе с шелухой зерно».
// 🆕🔑 И второй вопрос, купленный сменой 28: «а не отвечает ли за примечание то, что
// примечанием не является». Сторож правила 235 брал файл СЫРЫМ и искал в нём слова шапки —
// а слово нашлось в тексте ошибки живого кода. Он был зелёным по двум причинам разом,
// и одна из них случайная. Ищешь слова файла о себе — бери третий рез, а не сырой текст.
/**
* Вырезать ТОЛЬКО примечания, оставив строковые значения нетронутыми.
@@ -127,6 +132,24 @@ export function bezPrimechaniy(text) {
.replace(/\/\/[^\n]*/g, ' ');
}
/**
* Оставить ТОЛЬКО примечания, выбросив весь живой код. Обратный `bezPrimechaniy`:
* что вырезал тот, то оставляет этот, и наоборот. Обратность стережётся проверкой —
* `primechanie-ne-rashoditsya-s-pravdoy.test.mjs`, 1В.
*
* Берут его там, где надо спросить «а что файл ГОВОРИТ О СЕБЕ»: слова о подключении,
* наказы следующей смене, шапка. По сырому файлу такой вопрос задавать нельзя — за шапку
* ответит живой код, случайно совпавший словами (замерено сменой 28 на `items.mjs`).
*
* 🪤 Та же краевая грубость, что у обратного, и с тем же следствием наоборот: `//` внутри
* строкового значения (ссылка `https://`) этот рез сочтёт началом примечания и утащит
* хвост строки в «слова о себе». Сторожам это безразлично, берущему для нового дела — нет.
*/
export function tolkoPrimechaniya(text) {
const najdeno = String(text ?? '').match(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/g);
return najdeno ? najdeno.join('\n') : '';
}
export function tolkoKod(text) {
let out = '';
let i = 0;
+24
View File
@@ -615,6 +615,30 @@ export function masterskayaOtsechena({ potok, zakazanTools = false, zakazanMcp =
};
}
// 🔴🔴 ЖИВЫХ ЗОВУЩИХ НЕТ, И ЭТО НЕ ДОЛГ, А МЁРТВЫЙ ОСТАТОК. ЗАМЕРЕНО 07.08.2026, СМЕНА 28.
//
// Правило берёт ГОТОВЫЙ вывод целиком и разбирает его ПОСЛЕДНЮЮ строку. Дороги, которой
// оно писано — «запусти работника, дождись конца, возьми итог», — в затее больше нет:
// `startWorker` отдаёт ЖИВОГО ребёнка, а не текст. Все четверо, кто читает потоки, названы
// поимённо, и каждый делает ДРУГУЮ работу — сводить их сюда было бы не починкой, а поломкой:
//
// · `supervisor.mjs` — разбирает поток ЖИВЬЁМ, по строке, по мере прихода: попутно кладёт
// кусок на диск до всякого разбора и считает замеры запаса. Ждать конца ему нельзя;
// · `budget.mjs` (`probeUtilization`) — перебирает ВСЕ строки, ища замеры предела,
// а не итог. Последняя строка ему не нужна вовсе;
// · `sudya-nochi.mjs` (`itogIzPotoka`) — берёт ПОСЛЕДНЕЕ событие вида `result` (а не
// последнюю строку: после итога в потоке бывает хвост), понимает `is_error` и считает
// пустой приговор за «не судил». Отдаёт ТЕКСТ приговора, а не объект события;
// · `dengi-bez-zapisi.mjs` (`razborSleda`) — берёт ПЕРВУЮ цену из следа-улики.
//
// 🪤 Расход (`total_cost_usd`), ради которого правило писалось, затея НАРОЧНО не ведёт:
// остаток запаса она не считает, а СПРАШИВАЕТ у прибора системы (`supervisor.mjs`, разбор
// потока). Подключать отсюда второй счёт денег значило бы завести вторую дорогу к тому же
// смыслу — ровно то, от чего хозяйство предупреждает в пяти местах.
//
// 🔴 Не снесено НАМЕРЕННО, и причина названа: снос — необратимее записи, слова владельца
// на него нет, а вместе с правилом ушли бы три его проверки. Сносить это — отдельное
// решение владельца, не побочный ход чужой работы.
export function readResult(stdout) {
const text = String(stdout ?? '').trim();
if (!text) return null;
+24 -6
View File
@@ -80,7 +80,7 @@ import { readJsonOrBroken } from './state.mjs';
// (кусок 1, задача 11), и ввозить его не надо: второй такой же рядом развёл бы два
// правила на одно слово владельца. Здесь ввозятся только руки — кто читает каталог
// команд, кто снимает исполненную и кто называет непонятое слово.
import { readCommand, consumeCommand, unknownCommands, COMMAND_WORDS, closeItem, afterItemDone, afterTaskDone } from './items.mjs';
import { readCommand, consumeCommand, unknownCommands, COMMAND_WORDS, closeItem, afterItemDone, afterTaskDone, zadachaKonchena } from './items.mjs';
// --- дополнение к ввозам в шапке файла (кусок 4, задача 5) ---------------------
// 🔴 Замок «пункт закрыт» написан в item-audit.mjs, а ЗОВЁТ его этот файл, каждый круг,
// у каждого работника. До этой задачи он зеленел на своих проверках и в живой ночи
@@ -2235,11 +2235,29 @@ export async function supervise({
// в ночь»: одна ночь оставила бы два противоречащих знака, и утром владелец
// не знал бы, которому верить. Знак должен быть ОДИН.
//
// 🔴🔴 ДОЛГ КУСКА 7 (позиция 7): ИМЕННО ЭТА СТРОКА заменяется на зов
// `zadachaKonchena({ items, verified, otlozhennye }).konchena`, и вместе
// с долгом 5 (невыполнимые пункты), у которого своего ленджера в затее
// пока нет вовсе. Не переводи её тот, кто правит блок 5а.
const vseZaschitany = otlozhennyePunkty.size === 0;
// 🟢 ДОЛГ КУСКА 7 (позиция 7) ЗАКРЫТ 07.08.2026, СМЕНА 28. Здесь стояло
// упрощение `otlozhennyePunkty.size === 0` — «отложенных нет, значит всё
// засчитано». Оно врало в ТРЕТЬЕМ случае, безнадзорном: сюда приходят
// не только когда пунктов не осталось, но и когда пункт ЕСТЬ, а его файлы
// держит живой сосед и занять их из-под руки не вышло (`svobodnyy.item`
// гасится выше словами «их уводят из-под руки»). Отложенных при этом нет —
// и на НЕДОДЕЛАННОЙ задаче поднимался флаг приёмщику, а владельцу уходило
// «Пункты задачи кончились и все засчитаны». Замерено сторожем в
// `supervisor.test.mjs`: без правила `items_all_verified` = true.
//
// 🔴 Правило спрашивает ДРУГОЕ и потому ловит этот случай: не «нет ли
// отложенных», а «ВСЕ ЛИ пункты плана засчитаны». Оно же закрывает пустой
// план — прежде «ни одного пункта» читалось как «все засчитаны».
//
// 🪤 Долг 5 (невыполнимые пункты) сюда НЕ подключён и подключён быть не может:
// единственный источник невыполнимых — ответ владельца в `questions.mjs`,
// а он мёртв целиком и ждёт слова владельца. Довода `impossible` у правила
// нет вовсе — оно считает по засчитанным и отложенным, и этого хватает.
const vseZaschitany = zadachaKonchena({
items: punktyProgona.map(p => p?.id),
verified: closedItemsOfRun(runDirPath),
otlozhennye: otlozhennyePunkty,
}).konchena;
if (!vseZaschitany) {
const otlozheno = [...otlozhennyePunkty].join(', ');
+36
View File
@@ -2798,6 +2798,42 @@ describe('очередь пунктов и конец задачи (провер
expect(run.items_all_verified).toBe(false);
expect(run.reviewer_starts).toBe(false);
});
it('🔴🔴 пункт НЕ засчитан и НЕ отложен, но его файлы держит СОСЕД — приёмщику флага НЕТ', async () => {
// 🔴🔴 ТРЕТИЙ СЛУЧАЙ, И ОН БЫЛ БЕЗНАДЗОРНЫМ. Два соседних выхода охранялись: «все
// засчитаны» (флаг есть) и «пункт отложен» (флага нет). А «свободных пунктов не
// нашлось» бывает по ДВУМ причинам, и вторая — не «пунктов не осталось», а «пункт
// есть, но его файлы заняты живым соседом, и занять их из-под руки не вышло»
// (`svobodnyy = { item: null, … 'занять его файлы не удалось' }` в блоке 5а).
//
// Прежнее упрощение `otlozhennyePunkty.size === 0` в этом случае отвечало «все
// засчитаны» — потому что отложенных вправду нет. И тогда на НЕДОДЕЛАННОЙ задаче
// поднимался флаг приёмщику, а владельцу уходило «Пункты задачи кончились и все
// засчитаны». Пункт 2 при этом не тронут никем: сосед его только держит.
//
// 🔑 Ломается ТИХО и целиком невидимо — ровно тот класс, ради которого правило
// `zadachaKonchena` и написано: оно спрашивает не «нет ли отложенных», а «все ли
// пункты плана ЗАСЧИТАНЫ», и это разные вопросы.
zayavit('1');
// Сосед живой: карточка с углом на диске и веткой — иначе `liveForeignCorners`
// его не увидит, и замок не удержится (замерено в блоке про живых соседей выше).
const sosedRoot = join(dir, 'sosed-2');
mkdirSync(sosedRoot, { recursive: true });
writeJsonAtomic(join(rdir, 'workers', 'w-2', 'worker.json'), {
worker_id: 'w-2', run_id: 'r-1', status: 'working', workspace: sosedRoot, base_branch: 'feat/x',
});
// 🪤 Заявку кладём тем же способом, каким её кладёт жизнь, а не рукой в файл: ключ
// сравнения путей соберётся сам, и проверка не начнёт зависеть от его устройства.
expect(zanyatFayly(rdir, 'w-2', ['app/B.php'], {}).ok).toBe(true);
planPunktov4([
{ id: '1', title: 'Первый', tests: ['a.test.mjs'], files: ['app/A.php'], long: false },
{ id: '2', title: 'Второй', tests: ['b.test.mjs'], files: ['app/B.php'], long: false },
], ['w-1', 'w-2']);
await runSupervisor({ tickMs: 1000, script: [() => {}], head: () => 'sha-1', runTests: ZELENO });
const run = readJson(join(rdir, 'run.json'));
expect(run.items_all_verified).toBe(false);
expect(run.reviewer_starts).toBe(false);
});
});
describe('непризнанный пункт: работник узнаёт, и попыток у него две', () => {