45 KiB
⚠ ЭТО ХЕНДОФФ-ПРОМТ ДВИЖКОВОЙ СЕССИИ. Он живой и исполняется. Прочитай
CLAUDE.md, потом этот файл целиком, потом — его карту чтения (§3) и больше ничего, пока не появится вопрос. ⛔ ПАК ПАРНЫЙ. Твоя половина не лендится в одиночку: у платформы есть своя, и они принимаются вместе (D39.234п.2). Что это значит практически — §2 и §12.
Пак бэкенда: «ДВЕ ОСТАНОВКИ»
1. Какая проблема и что решит твой результат
Человек, который остановил перевод книги, сегодня рвёт всё, что летит к провайдерам прямо сейчас. Оплаченные генерации, которые провайдер уже начал, обрываются; движок честно списывает за них оценку, помечает позиции и на продолжении делает их заново — то есть платит второй раз за ту же работу. Владелец назвал это прямо: «нахрена обрывать соединение и запрос и посылать заново и терять до трети стоимости».
10.09 он ратифицировал форму (D39.234 п.1): остановок должно быть две.
- МЯГКАЯ — первое нажатие. Новых единиц работы не раздаём, начатое доигрываем, выходим сами. Ничего не рвётся, поэтому ни одна позиция не получает пометки «остановлено» и никто не платит дважды.
- ЖЁСТКАЯ — второе нажатие. Ровно сегодняшнее поведение: отмена контекста, сеттл оценок, пометки
cancelled. - Третье нажатие — дефолт процесса.
Твой результат: у человека появляются обе, они различимы машинно, и платформа их различает.
⚠ Это не «добавить фичу», а привести дерево к тому, что мы уже обещали. Контракт
docs/architecture/14-api-contract/openapi.yaml:762 говорит «finished work is kept and not paid for
again», а платформа в собственном коде пишет, что движок «finishes the in-flight chunk before exiting»
(platform/internal/runner/runner.go:56-58). Оба утверждения сегодня ЛОЖНЫ. Ты делаешь их истинными.
Носители в трекере: строки 381 (две остановки), 382 (пометка «оценка» не едет по шву),
377 (оплаченный обрыв, за которым ретрай успел, исчезает бесследно), 372 (кто отменил — человек
или упавший сосед), 375 (выгрузка читает остановленную позицию как выпавший текст), 379(а) (пин
на стенных часах). Их тела — в docs/BACKLOG.md, грепом по номеру.
2. Зона и git
Твоя зона записи — backend/. Ничего вне неё не трогаешь: platform/ делает своя сессия, docs/
— оркестратор. Коммитит только оркестратор: дерево готовишь и передаёшь на лендинг. Свои итоги,
вопросы и расхождения пиши в СВОЮ секцию docs/PROGRESS.md — это единственное исключение из «docs/ —
зона оркестратора».
⛔ Пара. Платформенная половина (mode у /stop, грейс, путь второго сигнала, чтение оценочных
строк) делается параллельно другой сессией. Ты её кода не пишешь и не правишь.
⛔ ФОРМУ КАДРА НЕ ПРОЕКТИРУЕШЬ — она уже ЗАФИКСИРОВАНА нотой D39.235 п.1, до начала работ и именно
затем, чтобы две зоны не решили по-разному. Читаешь её и делаешь РОВНО так (§4.7). Если считаешь форму
неверной — это вопрос оркестратору ДО кода, а не правка.
3. Карта чтения — ЗАКОН, дальше только по её ссылкам
backend/internal/pipeline/waverun.go— целиком. Здесь живёт мягкий ГЛАГОЛ, который ты будешь поднимать (latched, строки ~353–363), жёсткий (cancel(), ~404 и ~414) и обе пост-обработки волн.backend/internal/pipeline/stagerun.go— отrunStageдо конца денежного гейта: пер-вызовная резервацияr.gate.admit(...)(~570), ожиданиеwaitForSettle(~607), хартбит «still waiting for the provider» (~990).backend/internal/pipeline/reservegate.go— типwaitOutcomeи его исходы (~113–128).backend/cmd/tmctl/main.go— словарь кодов выхода (~96–104),signal.NotifyContext(~207),exitCode(~115–130); иbackend/internal/pipeline/events.go—terminal()(начало ~479, ветки исходов ~505–530: их ПОРЯДОК там сам объявлен утверждением, читай с начала функции).backend/internal/runevents/runevents.go— кадрFinished(~255+) и комментарий к полюVolume: он объясняет, ПОЧЕМУ у объёмного потолка нет своегоoutcome, и это твой прецедент.- ⛔ Тело ноты
D39.235п.1 —docs/architecture/05-decisions-log.md, греп^## D39.235. Это ФОРМА КАДРА, зафиксированная до начала работ, и она обязательна к исполнению буквой. Шестая позиция карты намеренно: без неё ты спроектируешь то, что уже решено, и разойдёшься с платформенной половиной.
Всё остальное — грепом по мере вопросов. Тела нот: docs/architecture/05-decisions-log.md, номер
грепается ^## D<номер>; статус одним хопом — реестр docs/architecture/05-decisions-index.md.
⚠ ПОДНОМЕР вида D39.234 п.1 собственного тела не имеет — это ПУНКТ внутри ## D39.234.
4. Состав и разметка свободы
4.1 Типизированный сентинел остановки — делай РОВНО так
Мягкая остановка контекст не отменяет. Значит errors.Is(err, context.Canceled) на ней ЛОЖНО, и
без своего типа она провалится в ветку «ошибок нет» — то есть прогон с недоделанными единицами уедет
как тихий успех. Это ровно ловушка, которую waverun.go уже называет своими словами («a draft-only
run reporting empty translations as exit-0 success»).
⇒ заведи сентинел (имя твоё, *StopRequested — предложение) со своим слотом в fail() волны,
рядом с тем, что уже есть у *CeilingHalt. Ранжирование при столкновении — твоё решение, но объясни
его в коде: паника старше остановки, остановка старше обычной ошибки — или иначе, если у тебя есть
довод. ⚠ Один случай ранжирования решён за тебя и лежит в §4.15(г) — прочти его прежде, чем выбирать.
4.2 Где именно мягкая остановка отказывает — делай РОВНО так
Точек отказа ДВЕ, и обе обязательны. ⛔ Исправлено по ревью до выдачи: одного гейта НЕДОСТАТОЧНО, и это замерено.
(1) Пер-вызовный денежный гейт — r.gate.admit(...) (backend/internal/pipeline/stagerun.go:570). Он
отсекает НОВЫЕ вызовы: элемент волны — это вся последовательность стадий, то есть несколько платных
вызовов подряд, и без гейта рабочий, уже держащий элемент, доиграет всю.
(2) Транспортная петля ретраев — retryLoop (backend/internal/llm/httpllm.go:187). ⛔ Без неё
ратифицированные ~20 минут НЕ ПОЛУЧАЮТСЯ: резервация берётся ОДИН раз на runAttempt
(stagerun.go:477, admit на :570), а client.Complete(ctx, …) (:699) держит под ней ВСЮ цепочку
ретраев — это говорит собственный комментарий кода: «This timer wraps client.Complete — the WHOLE
retry chain» (stagerun.go:984). ⇒ 503 на первой попытке → бэкофф → вторая попытка ПОД ТОЙ ЖЕ
резервацией, гейта никто не спросит, и ожидание становится ЦЕПОЧКОЙ (~64 мин), а не попыткой (~20).
retryLoop сегодня между попытками смотрит только ctx.Err() (httpllm.go:197) — мягкий стоп он
увидеть не может.
⇒ транспорт обязан видеть мягкую остановку МЕЖДУ попытками и в бэкоффе: сигнал «новых попыток не начинать», который читается только на границе попытки и НЕ трогает летящий запрос. Форма твоя (лишний параметр профиля, поле клиента, значение в контексте — выбери и обоснуй), но ограничение обязано стать ЗАПИНЕННЫМ, а не подразумеваемым.
⭐ Владелец ратифицировал именно ~20 минут (D39.234 п.1б): «Приемлимо». Цепочку ретраев не
доигрываем — ретрай это НОВОЕ решение о покупке.
⚠ Фидер волны при этом тоже латчится (новых единиц не начинаем) — но это следствие, а не механизм.
4.3 Прогонный источник латча — делай РОВНО так по форме, устройство твоё
Мягкий глагол уже построен и работает: latched в runWave, дословно «a different verb from
cancel(): closing it makes the feeder stop handing out indices — nothing new begins — while every
worker already holding an item runs it to completion under a context nobody cancelled».
⛔ Но он локален одному вызову runWave, а вызовов два (черновая волна и редакторская). Кнопке нужен ПРОГОННЫЙ источник, который каждая волна и каждый гейт спрашивают.
Это подъём существующего на этаж выше, а НЕ новый механизм (⚠ испр. до выдачи: вызовов runWave ровно ДВА — waverun.go:140 и :285; по главам волны НЕТ, прежняя редакция этого пункта учила неверной топологии) — если ты пришёл к выводу, что нужен новый,
напиши почему, прежде чем писать код.
4.4 Пост-обработка ОБЕИХ волн — по образцу потолка, не по образцу отмены — делай РОВНО так
Сегодня в waverun.go (~161–173) ветка «ошибка волны» разделена надвое: *CeilingHalt уходит через
r.reportEvicted(...) + r.moneyStoppedResult(...), любая другая ошибка — return nil, err мимо всего.
Мягкая остановка обязана идти по ПЕРВОМУ пути.
Причина названа там же в коде и она про деньги: черновая волна после остановки НЕ должна идти ни в майнинг банка, ни в редакторскую волну, потому что редакторская находит незапущенного члена с нулевым результатом и платит за редактуру единицы с дырой. Тот же класс уровнем выше: пере-сеять банк из половины книги значит сдвинуть снапшот редакторской волны для всех последующих прогонов.
Редакторская волна — свой путь (foldUnitOutcomes + noteMoneyStop, ~299–318 и ~928–933).
4.5 Банковые проходы идут ВНЕ волны — делай РОВНО так
runTerminologist (backend/internal/pipeline/terminologist.go:348, зовётся из mining.go:166) и
runBankRoleBatches (terminologist.go:907, зовётся из terminologist.go:465 и :666). ⚠ Испр. до
выдачи: прежняя редакция утверждала, будто латч их не остановит, потому что они идут мимо волны —
это ЛОЖЬ. Они идут через r.runAttempt(...) (terminologist.go:819), то есть через тот же гейт
r.gate.admit. Гейт их остановит — но ОШИБКОЙ посреди паса, а не усечением.
⇒ решение, которое принимаю я, а не ты: партии, уже купленные к моменту мягкой остановки, НЕ
применяются, пас выходит остановкой ДО применения. Довод тот же, что в §4.4: пере-сев банка из
половины двигает снапшот редакторской волны для всех последующих прогонов, а половина паса — это
половина банка. ⚠ И ничего не теряется: уже купленные партии лежат чекпойнтами и следующий прогон реплеит их за $0
(backend/internal/pipeline/terminologist.go, греп Already-paid batches cost nothing). Беречь
частичный результат применением — не бережливость, а тихий сдвиг снапшота.
У паса есть СВОЙ попартийный приём (terminologist.go:940–970), где он уже умеет решать «сколько
партий беру» и говорить об этом вслух — это и есть место, где мягкая остановка говорит «новых партий
не начинаем».
⚠ Читай там комментарий целиком прежде, чем править: он объясняет, почему этот пас УСЕКАЕТ, а не
ОТКАЗЫВАЕТ, и почему отказ классом Refusal был бы враньём платформе про деньги.
4.6 Эскалация вторым сигналом — делай РОВНО так
⛔ Сегодня второй Ctrl-C не делает НИЧЕГО, и это надо знать до того, как ты начнёшь.
signal.NotifyContext (main.go:207) поднимает горутину, которая после ПЕРВОГО сигнала выходит;
регистрация снимается только defer stop() (~208); канал ёмкостью 1. ⇒ второй сигнал падает в
переполненный буфер и никем не читается, процесс не умирает. То есть «второе нажатие ужесточает» —
новое поведение, которое ты пишешь, а не восстановление дефолта.
Форма ратифицирована (D39.234 п.1а): первый сигнал — мягкая, второй — жёсткая (сегодняшний путь),
третий — дефолт процесса. Устройство твоё; NotifyContext для этого мал, и это нормально.
⚠ Платформа шлёт ровно ОДИН SIGTERM (platform/internal/runner/runner.go, systemctl stop). Значит
её «остановить сейчас» — это второй сигнал, который она пошлёт отдельно. Твоя часть — чтобы второй
сигнал делал жёсткую остановку независимо от того, человек его послал или платформа.
4.7 Кадр finished: числа, а не новое слово — делай РОВНО так
⛔ Коды выхода НЕ ТРОГАТЬ. Оба стопа — 5. Полоса заморожена («a fresh code would be a word added to
a ratified dictionary»), и на 5 завязана логика платформы: platform/internal/ingest/exit.go:34
= ExitStopped = 5.
⛔ И нового значения Outcome тоже НЕ заводить. Прочитай комментарий к полю Finished.Volume
(runevents.go:255+) — там разобран ровно этот соблазн и объяснено, почему он неверен: словарь
Outcome читается из ОБОИХ каналов, каждое значение маппится в код выхода, и значение, живущее только
в потоке, заставило бы два канала сообщать РАЗНЫЙ исход одного прогона. У чисел словаря нет, поэтому
факт едет числами — присутствие поля и есть признак.
⇒ различие «мягко / оборвано» едет ПОЛЕМ С ЧИСЛАМИ, и форма зафиксирована D39.235 п.1 — делай РОВНО так:
Finished.Stop{ mode: "soft"|"hard", in_flight_finished, in_flight_cut, cut_estimated_micro_usd }. ⛔ Правило присутствия — «есть, если остановку ЗАПРОСИЛИ», а НЕ «если исход stopped». Прецедент дословно у соседнего поля:Moneypresent iff a ceiling was REACHED, not iff the outcome is ceiling. Это важно ровно в случае §4.15(г): остановку запросили, а прогон уехалfailedиз-за соседа — поле обязано БЫТЬ.estimated{ rows, micro_usd }едет на ОБОИХ кадрах — и на кумулятивномspend, и вMoneyтерминального. Довод: платформа отвечает пользователю «не больше Y» ВО ВРЕМЯ прогона, а число, появляющееся только в конце, на этот вопрос не отвечает.Moneyна исходеstoppedедет всегда, когда счётчики есть. Сегодня онnilне только без потолка, но и когда волн ещё не было (backend/internal/pipeline/events.go, грепmoneyLedger): стоп до волн — ингест, сид — счётчиков не имеет, и это законная пустота, а не умолчание.- Минор словаря потока
1.3 → 1.4(backend/internal/runevents/runevents.go, грепStreamVersion). ⚠ Платформа поднимает СВОЮ константу сама — это её половина, не твоя.
4.8 Строка 377 — оплаченный обрыв, за которым ретрай УСПЕЛ, исчезает бесследно
backend/internal/llm/httpllm.go, ~189–191: на успешном ретрае if err == nil { return resp, nil }, и
накопленный owedCut ВЫБРАСЫВАЕТСЯ. Провайдер спрошен дважды (первый вызов доставлен, подтверждён и
оборван), вызывающий получает чистый успех и о первом не узнаёт ничего — ни строки леджера, ни оценки,
ни пометки.
⭐ Владелец велел делать её ЭТИМ ЖЕ заходом (D39.234 п.1в), и довод не в сумме. D39.230 п.2
оставил открытым вопрос «биллит ли вендор оборванное» и держит его на сверке инвойса вендора с суммой
оценочных строк. Невидимая вторая генерация ломает эту сверку по построению: сверять нечем.
Форма починки твоя. ⚠ Ни одна из девяти фикстур этого пути не покрывает
(TestABrokenConnectionAfterDeliveryIsRetriedOnceAndOnlyOnce роняет ОБЕ попытки) — значит фикстуру
пишешь новую, и она обязана доказать, что второй запрос УСПЕЛ.
4.9 Строка 375 — третье состояние единицы — делай РОВНО так
Выгрузка сегодня читает строку cancelled как ВЫПАВШЕГО ЧЛЕНА редакторской единицы
(memberDrops, backend/internal/pipeline/status.go:534, живой вызов один —
backend/internal/pipeline/export.go:307) и сообщает читателю «из единицы пропал текст», тогда как
пропала лишь незаконченная работа.
⛔ Простого фильтра НЕДОСТАТОЧНО, ЕСЛИ сценарий ниже достижим — и доказать это твоя работа. ⚠ Испр. до выдачи: прежняя редакция называла это «замерено движковой зоной» — носителя такого замера НЕТ (наряд ставит по этой позиции «пинг», в docs/PROGRESS.md следов нет). Это рассуждение, а не замер, и доказать сценарий фикстурой — твоя работа. Если применить
resolvedForResume (cutcall.go:253) и на этом остановиться, единица уйдёт ok, МОЛЧА не досчитавшись
текста члена: DroppedMembers = 0, DroppedReason пуст. Это обмен ЛЖИ на МОЛЧАНИЕ, и верно ни то ни
другое. ⇒ нужен ТРЕТИЙ признак: «единица неполна ИЗ-ЗА ОСТАНОВКИ».
⭐ Попутно фильтр чинит артефакт порядка: внутренний цикл оставляет ПОСЛЕДНЮЮ флагнутую черновую строку,
строки приходят ORDER BY … stage, и при двух черновых стадиях cancelled может перебить содержательную
причину.
⛔ И вот ЗДЕСЬ прежняя редакция ошибалась опаснее всего: случай «cancelled затёр настоящую причину»
ВОЗМОЖЕН. Апсерт по (book_id, chapter, chunk_idx, stage) (backend/internal/store/chunkstatus.go:93)
не добавляет строку, а ЗАМЕНЯЕТ её. Сценарий (выведен по коду, фикстурой не гонялся — прогони сам):
единица c-lite, где член выпал по length, а редактор отработал ok по остатку → redrive черновика
этого члена (ResetChunkStages сбрасывает только флагнутую стадию, ok-стадии никогда) → мягкой
остановки над ним нет, а ЖЁСТКАЯ пишет cancelled поверх length → и фильтр §4.9 спрячет НАСТОЯЩИЙ
дроп. ⇒ починок ДВЕ: третий признак «неполна из-за остановки» И запрет cancelled затирать флагнутую
строку (колонка first_flag_reason для этого уже есть). Форма твоя; сценарий обязан быть предъявлен
фикстурой.
4.10 Строка 372 — четвёртая причина аборта ожидания — делай РОВНО так
waitOutcome (reservegate.go) сегодня разводит исходы ожидания, и его собственный комментарий
рассказывает, какая ошибка на этом месте уже была совершена: с bool «ничего не летит» и «контекст
умер» читались одинаково, и прогон, остановленный человеком, публиковал ceiling-событие с нехваткой —
то есть просил у платформы денег за остановку, к деньгам отношения не имевшую.
Мягкая остановка — ЕЩЁ ОДНА причина, и вместе с ней решается 372: «кто отменил — человек или упавший сосед» превращается в «мягко / жёстко / сосед».
⚠ Мелочь, которую заметь и почини заодно: комментарий над типом говорит «THREE values», а констант
там четыре — waitNotAllowed добавили позже и текст не поправили. Ты добавляешь пятую.
4.11 Строка 382 — пометка «это оценка» не едет по шву — делай РОВНО так по своей половине
Владелец разрешил списывать по ОЦЕНКЕ при УСЛОВИИ, что пометка стоит (D39.230 п.1). В движке она
есть: backend/internal/pipeline/status.go:321 — estimated_rows / estimated_usd в status --json.
Дальше она обрывается: в кадрах backend/internal/runevents/runevents.go слова estimated 0 хитов
(контроль: committed в том же файле — 6; денежный кадр несёт один committed_micro_usd, ~244).
Платформа её не читает и прямо просит: platform/internal/pgstore/credits.go:235 — «publish the count
and sum of estimated-price rows beside committed_usd, which is what would let this side say "at most Y"».
⇒ твоя половина — довезти счёт и сумму оценочных строк до кадров. Чтение — платформенная половина.
Форма — та же D39.235 п.1, что в §4.7: estimated{rows, micro_usd} на spend И в Money. Ничего не
согласовывай отдельно: это один шов, и он уже описан нотой.
4.12 Строка 379(а) — пин, решаемый окном стенных часов — бери, раз всё равно трогаешь
backend/internal/pipeline/cutcall_test.go:402 — две строки таблицы девяти («after 2xx · cancelled by
the operator» и «before headers · cancelled by the operator») разводятся <-srv.arrived +
time.Sleep(50 * time.Millisecond) против attempt_s: 1. По какую сторону границы заголовков сел
cancel(), не утверждается ничем; на мутанте это давало 2 красных из 8.
Ты в любом случае перестраиваешь семантику остановки, значит эти строки твои. Фикстура обязана строиться так, чтобы предмет был НЕИЗБЕЖЕН, а не вероятен.
4.13 Оператору — сказать, чего он ждёт — реши сам, но молчать нельзя
⚠ Испр. до выдачи: «не печатает НИЧЕГО» было буквально неверно — при отмене пайплайн пишет WARN
(backend/internal/pipeline/cutcall.go:214), и tmctl печатает ошибку на выходе. Настоящих дыры две,
и вторая дороже: (1) в момент сигнала подтверждения нет — человек нажал и не понял, случилось ли
что-нибудь, а через двадцать секунд нажмёт снова и получит жёсткую, потеряв ровно те деньги, ради
которых мягкая заводилась; (2) ⛔ на стоп-пути НЕ рендерится леджер: частичный результат
translate показывает только для подписного стопа и потолка (backend/cmd/tmctl/main.go, греп
WaveSignatureStop и CeilingHalt), а результат остановленного прогона выбрасывается. Мягкая
остановка по §4.4 результат ПРОИЗВОДИТ (moneyStoppedResult) — и до оператора он не доедет.
⇒ при первом сигнале печатается строка вида «остановка запрошена: N вызовов в полёте, самый долгий
дедлайн Xs; ещё раз — оборвать их (спишется оценка)». Опора готова: хартбит «still waiting for the
provider on a call in flight» (stagerun.go:990) уже знает и число, и дедлайн.
4.15 Пять мест, которые ревью назвало «без них не сойдётся» — делай РОВНО так
(а) ⛔ ГРАНИЦЫ ФАЗ, а не только волны. §4.4 покрывает остановку, пришедшую ВО ВРЕМЯ волны. Пришедшая
МЕЖДУ фазами уводит прогон платить за редактуру: источник обязаны спрашивать переходы — после черновой
волны → перед майнинг-стопом → перед подписным стопом → перед редакторской волной, включая
авто-продолжение с пере-севом (backend/internal/pipeline/mining.go, греп bank-mining/auto-continue).
(б) res.Volume = nil на мягкой остановке — как это делает потолок. Плановые счётчики после
остановки посреди работы врут; довод дословно — в комментарии к полю Finished.Volume.
(в) Статус джоба при отказе гейта. Сегодня отказ даёт failed (backend/internal/pipeline/stagerun.go,
греп mandatory/статус после admit). Для мягкой остановки это НЕВЕРНОЕ слово: ничего не сломалось.
(г) Столкновение мягкой остановки с падением соседа — решение приняло за тебя. Инфра-ошибка соседнего
рабочего зовёт cancel() (waverun.go, ветка fail) и рвёт ровно те вызовы, которые мягкая остановка
берегла. Это приемлемо — провайдер сломан, беречь нечего, — но исход прогона тогда failed С
пометками cancelled, и так и должно быть. Не строй защиту от этого; запинь, что так и есть.
(д) Пин на резюм после МЯГКОЙ остановки: ноль платных вызовов по уже отвеченным стадиям, ничего не пере-делывается. Тривиально по построению — и ровно поэтому должно быть проверено, а не подразумеваться.
4.14 Чего в паке НЕТ — не делай, и это ОБЪЯВЛЕННЫЕ сужения
- Платформенная половина — чужая зона и чужая сессия. Ты не правишь
platform/вообще. - Коды выхода и словарь
Outcome— заморожены (§4.7). - Продуктовый вопрос «сколько ждать по умолчанию» — решён владельцем (~20 мин, одна попытка). Не пере-открывай; если у тебя есть довод против — это вопрос оркестратору, а не правка.
- Строки 373 и 376 (инертный дедлайн на HTTP/2; доставленный обрыв до заголовков книжится нулём) —
в паке НЕТ. У 376 направление недосчёта ратифицировано (
D39.196п.2а), у 373 нужен пир, не дренящий тело. - Вердикт главы — трогать НЕ надо: замером 10.09 установлено, что остановка его уже не портит
(
D39.233п.2 — там замер; пинTestAStoppedPositionIsNotADecidedUnitзелён).
5. Мандат самопроверки ИСПОЛНЕНИЕМ — «перечитал сам» не считается
Это норма проекта и решение владельца, не пожелание. Самоотчёт «проверено» без исполнения здесь регулярно оказывался ложным и стоил денег.
- Baseline СВОИМ прогоном ДО первой правки:
make battery— запиши MAKE-EXIT, числоok, число FAIL, «no test files» и число скипов поимённо. ⛔ Прогон без-vпрячет скипы: однажды 431 скип проехали как зелень, и среди них были все пины предмета. python3 docs/scripts/counts.py --check— до и после.make mutations— снимается отдельно; число внеси в итоговый отчёт вместе с составом.- Мутируй СВОИ новые тесты. Линза качества трижды находила дыры в тестах, которые их автор считал сильными.
- ⛔ Мутация засчитывается по ТЕКСТУ падения, а не по факту красноты. Читай текст: говорит ли он про сломанное тобой. Правый вердикт по неправой причине — дыра, а не поимка.
- ⛔ Пин, ФЛЕЙКОВЫЙ на мутанте, измеряет пустой сценарий. Денежный пин гоняется не один раз (8 прогонов — рабочее число этой смены), и фикстура строится так, чтобы предмет был НЕИЗБЕЖЕН.
- ⛔ Пин может удовлетворяться ЧУЖОЙ уликой, и это не флейк, а детерминированная пустота. На мутанте спрашивай не «покраснело ли», а ЧТО ИМЕННО удовлетворяло утверждение.
- ⛔ Отрицательный замер обязан ДОКАЗАТЬ, что спросил существующее: рядом с нулём ПЕЧАТАЕТСЯ контрольная величина. Не «проверено с контролем», а число.
- ⛔ Копия под мутацию защищается ПОСТРОЕНИЕМ (
test -f go.mod+ сверкаpwd), а неset -e: 10.09cdв несозданный каталог провалился,set -eне удержал, и мутация ушла в НАСТОЯЩЕЕ дерево.
Адверсариальный проход по СВОЕЙ готовой работе — отдельным заходом, свежим взглядом. Что в ЭТОМ паке уязвимо, направление даю:
- Гонки. Мягкая остановка — это состояние, которое читают несколько горутин. Латч в
runWaveсделан КАНАЛОМ, а не флагом, и в коде объяснено почему (фидер БЛОКИРУЕТСЯ на отправке ровно в том состоянии, в котором остановка и наступает). Твой прогонный источник обязан удержать то же свойство. - Тихий успех. Главный риск пака (§4.1). Спроси у своей работы: может ли прогон с недоделанными единицами уехать с кодом 0?
- Двойной учёт. Мягкая остановка не платит — значит ни одна пометка
cancelled, ни один сеттл оценки на ней возникать НЕ должны. Если возникают — ты построил жёсткую под мягким именем. - Тест, который измеряет время вместо предмета (§4.12) — не повтори его в новых фикстурах.
6. Оси ревью — характер «Код», плюс деньги и шов
Ось вправе заменить с аргументом. По умолчанию: деньги (кто и когда платит), шов (что видит
платформа), конкурентность (латч и его читатели), словарь исходов (коды выхода и Outcome
неприкосновенны), тесты (не измеряют ли они соседнюю величину).
7. Записка-план ДО первой правки
Напиши в свою секцию docs/PROGRESS.md порядок работы и почему он такой, ДО того как тронешь код.
Рекомендованный: §4.1 сентинел (без него ни один другой пункт не проверяется) → §4.3 источник → §4.2
точка отказа → §4.4/§4.5 пост-обработка → §4.6 эскалация → §4.7 кадр → дальше строки.
⛔ ДВА ЛЕНДИНГА, линия объявлена заранее. Пак большой: транспорт, пайплайн, tmctl, кадр событий плюс конкурентность. Резать его на две СЕССИИ нельзя — §4.2 и §4.8 правят одну функцию retryLoop. Поэтому одна сессия, но два лендинга: первый — §4.1–4.7, §4.10, §4.12, §4.13, §4.15 и кадр; второй — §4.8 (строка 377) и §4.9 (строка 375). ⚠ Кончившийся контекст после первого лендинга — штатный исход, а не провал: остаток уходит новым промтом новой сессии, и это заранее разрешено. Владелец велел делать 377 «этим же заходом» — речь была о ПОРЯДКЕ работ, а не о размере одного лендинга.
8. Заявление = команда
Любое «сделано / проверено / закрыто» в отчёте сопровождается КОМАНДОЙ, которой это можно воспроизвести, и её выводом. Утверждение без команды не считается сделанным.
9. Эхо-протокол старта
Первым сообщением верни: (а) одной фразой — в чём разница между двумя остановками; (б) какой пункт §4 ты считаешь самым опасным и почему; (в) что в этом паке ты считаешь неверным или недоказанным (право сказать это у тебя есть и им пользуются).
10. Что НЕ удалось — обязательная секция отчёта
Пустой она не бывает. Если пуста — ты не искал.
11. Канал вопросов и право сказать «этого делать не надо»
Вопрос — секцией в свою часть docs/PROGRESS.md; оркестратор читает. ⚠ Слово, живущее только в
переписке, умирает вместе с сессией: этой смене это стоило трёх потерянных диспозиций (D39.231 п.1).
Всё, что должно пережить твою сессию, пишется в репозиторий.
Если считаешь, что пункт пака неверен, — скажи, а не обходи молча. Два пункта прошлого пака оказались неверны, и оба были ошибками оркестратора.
12. Критерий завершённости — по нему тебя примут
- Две остановки существуют, различимы человеком и машиной, и мягкая не создаёт ни одной пометки
cancelled— это проверяется тестом, а не рассуждением. - Прогон с недоделанными единицами не может уехать как успех.
- Ожидание мягкой остановки ограничено ОДНОЙ летящей попыткой, и это ограничение запинено.
- Второй сигнал даёт сегодняшнюю жёсткую остановку, третий — дефолт процесса (проверяется, а не подразумевается).
- Кадры несут числами, мягко или жёстко всё кончилось, и счёт с суммой оценочных строк — в форме
D39.235п.1, буква в букву, включая правило присутствияStopи минор1.3 → 1.4. - Строки 377, 375, 372, 379(а) закрыты, каждая с предъявленной уликой (377 и 375 — вторым лендингом, §7). 6-бис. Резюм после мягкой остановки делает ноль платных вызовов по отвеченным стадиям — запинено (§4.15д).
- Батарея зелёная целиком (все пакеты, скипы названы),
counts.py --checkзелёный, мутационный прогон снят с числом. - Отчёт: что не удалось, что ты изменил против пака и почему, какие числа сняты какой командой.
Деньги
Пак $0: платных вызовов не требует и не разрешает. Всё проверяется фикстурами. Если тебе кажется, что нужен живой провайдер, — это вопрос оркестратору, а не решение сессии.
Отчёт
Своя секция docs/PROGRESS.md. Числа — командами. Ничего не коммить.