211 lines
21 KiB
Markdown
211 lines
21 KiB
Markdown
# ДОФИКС бэкенд-паку «число согласия на пути второй покупки» — по приёмке оркестратора №22 (03.09.2026)
|
||
|
||
> **Роль:** бэкенд, та же сессия, что отработала пак. **Зона записи:** `backend/` + своя секция
|
||
> «Бэкенд» в `docs/PROGRESS.md`. Чужие зоны читаешь, не правишь. **Не коммитишь** и не трогаешь индекс
|
||
> и историю — запреты и причина: `CLAUDE.md`, «Git-координация мультисессий».
|
||
> ⛔ **В индексе сейчас лежит НЕЗАКОММИЧЕННАЯ работа двух смен (46 файлов).** Ни одной пишущей
|
||
> git-команды: ни `checkout`, ни `restore`, ни `stash`, ни `reset`, ни `clean`, ни `add`, ни `commit`.
|
||
|
||
## Что это и почему оно пришло
|
||
|
||
Пак **принят с дофиксом**. Приёмка шла не чтением отчёта: десять линз (пять на зону), каждая обязана
|
||
была проверять исполнением, каждая находка ушла опровергателю с мандатом её ОПРОВЕРГНУТЬ своей
|
||
командой. По бэкенду 20 находок, 19 пережили опровержение.
|
||
|
||
⚠ **Твой код в основном подтверждён — ниже отдельная секция «что приёмка проверила и признала», и её
|
||
переделывать не нужно.** Дофикс про другое: пак создал один дефект в том, что сам чинил, оставил
|
||
ложным одно РАТИФИЦИРОВАННОЕ утверждение, и три свойства, объявленные твоими, тестом не прибиты —
|
||
мутации приёмки их пережили.
|
||
|
||
## Карта чтения — три позиции
|
||
|
||
1. Этот файл. Твой прежний промт `docs/BACKEND_CONSENT_SESSION_PROMPT.md` — уже прочитан, перечитывать
|
||
целиком не надо.
|
||
2. Свой отчёт в `docs/PROGRESS.md`, секция «Бэкенд», запись 03.09 — по пунктам Б7–Б10 правится он сам.
|
||
3. Улики ниже. **Каждая проверена приёмкой исполнением; воспроизведи их сам** — если что-то не
|
||
воспроизводится, это находка, неси её, а не подгоняй работу под текст.
|
||
|
||
## Б1 [важное] — стоп-строка и лог лгут ровно в том случае, который пак СОЗДАЛ
|
||
|
||
`bound()` теперь истинен при `Carried > 0`. Значит прогон, дошедший **до конца книги** и лишь
|
||
дописавший начатое, печатает оператору самопротиворечие:
|
||
|
||
```
|
||
backend/internal/pipeline/volume.go:220 — «stopped on the VOLUME ceiling (--max-units %d),
|
||
not on money and not at the end of the book» — префикс БЕЗУСЛОВЕН, внутри самого fmt.Sprintf
|
||
и тут же в том же предложении: «Still in the book: 0 unit(s) NEVER delivered»
|
||
```
|
||
|
||
Воспроизведено живым прогоном настоящего `Runner` (две главы, первый прогон умирает на редакторе,
|
||
второй с `MaxUnits=2`): `Left()=0 granted()=0 Paid()=2`, и обе половины предложения печатаются.
|
||
Тот же текст едет в лог `waverun.go:173`, а комментарий у вызова `waverun.go:169-170` («Only set when
|
||
the ceiling actually held work back») протух — **в файле, который пак правил**.
|
||
|
||
**Делай РОВНО так:** предложение разводится на два случая — грант кончился / книга кончилась.
|
||
**Решаешь сам:** формулировки обеих веток и нужен ли отдельный текст для смешанного случая.
|
||
⚠ Пункт 2 пака закрывался ИМЕННО как дисклоужер — объявлять его закрытым до этой правки нельзя.
|
||
|
||
## Б2 [важное] — ратифицированный контракт кадра `finished.volume` стал ЛОЖНЫМ
|
||
|
||
`D39.181 п.2` объявляет ПРИСУТСТВИЕ поля булевым признаком «грант придержал работу»:
|
||
|
||
```
|
||
backend/internal/runevents/runevents.go:189 «present only when a volume grant actually held work back»
|
||
backend/internal/runevents/runevents.go:202-204 «its PRESENCE is the boolean: the engine attaches this
|
||
only when the grant actually held something back»
|
||
те же утверждения — backend/internal/pipeline/events.go:584-588 и bookrun.go:91
|
||
```
|
||
|
||
Кадр теперь приезжает и у прогона, дошедшего до конца книги (`left_fresh:0`, `left_rework:0`), и
|
||
`carried` на провод не выходит вовсе — то есть читатель различить два состояния НЕ МОЖЕТ ничем.
|
||
Пак переписал соседние поля ТОЙ ЖЕ структуры (`Delivered`, `Free`), а утверждение тремя строками выше
|
||
оставил. Сегодня безопасно только потому, что потребителя нет: `platform/internal/ingest/events.go`
|
||
декодирует `Finished{Outcome}` и всё.
|
||
|
||
**Делай РОВНО так:** текст контракта приводится в соответствие с поведением ВО ВСЕХ ЧЕТЫРЁХ местах.
|
||
**Решаешь сам:** формулировка. ⛔ **Поля на провод НЕ добавлять** — это класс «новое поле в
|
||
опубликованном документе», он требует ратификации и минора `StreamVersion`, и владелец 03.09 уже
|
||
сказал «оставить как есть». Если считаешь, что честный контракт без поля невозможен, — **пинг**, а не
|
||
поле.
|
||
|
||
## Б3 [важное] — три свойства объявлены твоими и НЕ прибиты: мутации приёмки пережили весь пакет
|
||
|
||
Все три посажены приёмкой на копии дерева, каждая SURVIVED на `./internal/pipeline/`, и в том же
|
||
прогоне соседние посадки дали RED — то есть положительный контроль стоит.
|
||
|
||
- **(а) `volume.go:191`** — `case class == unitFresh && s.stop.Delivered > s.stop.Carried:`. Возврат к
|
||
форме `> 0` (и эквивалентной `>= 1`) переживает пакет. Существующий `TestReconcileKeepsCarriedInsideDelivered`
|
||
(`volumeslot_test.go:554`) гоняет только ветки `unitFree` и до этой ветки не доходит.
|
||
⚠ Отчёт числит за этой находкой «посадку **Y3**», **которой в репозитории нет** — см. Б7.
|
||
- **(б) `rebill.go:450`** — подмена `projectionBasis(proj)` на `projectionBasis(book)` переживает пакет.
|
||
Ветка-близнец (scoped-клауза, `:505`) от той же подмены КРАСНЕЕТ через
|
||
`TestTheScopedClauseOfTheRefusalCarriesItsOwnBasis` — значит пакет эту подмену ловить умеет.
|
||
- **(в) `rebill.go:323`** — `if p.ModelMovedRows > 0` заменяется на `>= 0`, и «0 of the N unit(s) were
|
||
bought from a model…» уезжает в КАЖДЫЙ текст согласия; пакет зелёный. У близнеца `HistoricalRows`
|
||
отрицательный пин ЕСТЬ (`reprice_test.go:569-572` проверяет, что чистая сумма НЕ содержит
|
||
«originally billed»), у новой оговорки — нет нигде.
|
||
|
||
**Делай РОВНО так:** каждое свойство получает тест, который от своей мутации краснеет, И запись в
|
||
`backend/cmd/tmmutate/mutations.json`. **Решаешь сам:** форма тестов и формулировки записей.
|
||
⚠ Приёмка сажала СВОИ формы мутаций — не копируй их, посади свою и убедись, что она красная.
|
||
|
||
## Б4 [мелочь] — докстринг `classifyUnits` не пере-считан под собственную правку
|
||
|
||
`volume.go:637`: «sorts every output unit into the **three** things this run can do to it», а `unitClass`
|
||
несёт ЧЕТЫРЕ члена, и добавленный паком `unitCarried` присваивается ВНУТРИ самой этой функции.
|
||
Вредно адресно: читатель, ищущий, куда попадает начатый-но-не-отгруженный юнит, по слову «three»
|
||
решит, что четвёртого класса нет, — и пойдёт искать в ветку `unitFresh`, ровно туда, где жил дефект
|
||
строки 232. **Делай РОВНО так**, правка на одно слово.
|
||
|
||
## Б5 [мелочь] — пример в `backend/README.md:73` противоречит новой семантике
|
||
|
||
Строка обещает «оплатить не больше 10 выходных юнитов», тогда как тело README:11 новую семантику
|
||
раскрывает верно. **Делай РОВНО так:** привести пример к телу.
|
||
|
||
## Б6 [мелочь, по желанию] — шов `store → репрайсер` прибит с одной стороны
|
||
|
||
Подмена `c.escalation` на `0` в `ledger.go:380` переживает `./internal/store/`, а `./internal/pipeline/`
|
||
краснеет через `TestAnEscalatedRowIsRePricedAtTheNewHopEndToEnd`. Дефектом сегодня не является — пин
|
||
живёт там, где живёт свойство. Но твой же довод по фильтру книги («мутация пережила обе батареи по
|
||
отдельности, значит одной мало» — оттого FC1-book-filter-**store** И -**pipeline**) применим и здесь.
|
||
**Решаешь сам:** заводить ли запись каталога на store-стороне; отказ аргументируй.
|
||
|
||
## Б7 [среднее] — отчёт ссылается на ПЯТЬ несуществующих посадок как на живые гейты
|
||
|
||
`Y2-silent-when-nothing-left` · `Y3` · `Z2` · `X29` · `Y4`. Каталог получил ровно ШЕСТЬ записей
|
||
(`FC1-*`×4, `FC5-*`×2); 35 заявленных собственных посадок жили в scratch-каталогах
|
||
(`own_mutations.json`, `round2_mut.json`, `round3_mut.json`, `draftonly_mut.json`), которых на машине
|
||
нет. Проверено грепом с границами слова, положительный контроль на ту же форму: `FC1-book-filter-store`
|
||
находится в двух местах.
|
||
|
||
Читатель отчёта принимает пять несуществующих гейтов за живые — а один из них «закрывает» гарантию,
|
||
чья мутация переживает (Б3а). **Это ровно та болезнь, ради которой `tmmutate` и лежит в репозитории.**
|
||
|
||
**Делай РОВНО так:** либо снять ссылки, либо посадить недостающие записи в каталог.
|
||
**Решаешь сам:** какие из пяти стоят записи, а какие были разведкой.
|
||
|
||
## Б8 [мелочь] — четыре числа отчёта не печатаются командами, которые он для них называет
|
||
|
||
- «денежного пути в каталоге было 0 файлов, стало **5**» — каталог даёт 18 → 21 различных файла, то
|
||
есть ТРИ новых (`reprice.go`, `volume.go`, `ledger.go`), шесть записей; греп из промта печатает 7.
|
||
Ни одно прочтение не даёт 5.
|
||
- «на входе **3** попадания» у грепа `can never quote a different number` — на входном коммите в
|
||
названной области попадание ОДНО.
|
||
- «все **18** строк» линта — гейт печатает **19**, и девятнадцатый создан самим отчётом
|
||
(строка, цитировавшая мёртвый якорь в самом отчёте).
|
||
- «в файле **485** строк» — 499: число снято до того, как отчёт дорос.
|
||
|
||
**Делай РОВНО так:** пере-считать каждое своей командой и вписать команду рядом с числом.
|
||
⚠ `python3 docs/scripts/counts.py --lint` гнать **ПОСЛЕДНИМ действием**, после того как отчёт дописан,
|
||
и починить 19-й якорь — иначе гейт остаётся красным по вине самого отчёта.
|
||
|
||
## Б9 [мелочь] — неполнота самообъявления по D39.183
|
||
|
||
`TestCheckpointsOfOneGenerationSumToTheRowsCost` (`reprice_test.go:639`) адаптирован под третий возврат
|
||
`usd()`, а в блоке самообъявления не назван, хотя остальные четыре адаптации под ту же смену сигнатур
|
||
перечислены поимённо. Ослабления нет — правка механическая. Это ровно класс, который бэклог держит
|
||
строкой **248**. **Делай РОВНО так:** дописать имя.
|
||
|
||
## Б10 [к сведению, НЕ заказ] — что приёмка нашла и что сознательно НЕ чинится этим дофиксом
|
||
|
||
Назови это в отчёте, чтобы оно не умерло молча вместе с паком.
|
||
|
||
- **`Carried` на провод не выходит**, а подсказка, дописанная паком в докстринг `Delivered`, ключуется
|
||
на `delivered > max_units` — и в форме «весь остаток перенесён» `delivered == max_units`, то есть
|
||
подсказка НЕ срабатывает и разложить кадр читателю нечем. Связано с открытым вопросом владельца о
|
||
семантике переноса; чинится вместе с ним, а не отдельно.
|
||
- **В перенос попадает и юнит, который ФЛАГНУЛСЯ и текста не отдал** — он «начат и не отгружен»,
|
||
значит каждая следующая покупка снова платит за его пере-атаку ВНЕ гранта. Приёмка это вывела из
|
||
`classifyUnits` и подтвердила зелёными тестами пака, но **цикла из трёх последовательных покупок не
|
||
гонял никто**. Это вход в решение владельца, а не дефект — не чини, но не потеряй.
|
||
|
||
## Границы — что НЕ делать
|
||
|
||
- ⛔ **Не добавлять поле на провод** (см. Б2) и не двигать `StreamVersion`, форму манифеста, схему
|
||
хранилища, версию контракта.
|
||
- ⛔ **Не менять семантику переноса слота.** Владелец 03.09 отложил её словом «подумаем на этот счёт».
|
||
В коде сегодня «перенос ограничен грантом», и приёмка это ЗАМЕРИЛА: грант 2 оплачивает 4 выходных
|
||
юнита, 6 вызовов провайдера. Это открытый вопрос владельца, а не дефект — трогать нельзя.
|
||
- ⛔ **Не править `docs/architecture/15-money-path.md` и тело `D39.170`** — они противоречат новой
|
||
семантике `--max-units`, это правда, но это зона оркестратора, и он их правит сам. Ты правильно
|
||
сделал, что не полез.
|
||
- ⛔ Не трогать чужие зоны, `.env`, соседний worktree.
|
||
|
||
## Что приёмка ПРОВЕРИЛА и признала — переделывать не нужно
|
||
|
||
- Батарея зелёная: `EXIT=0`, `0 issues`, три известных SKIP, ни одного FAIL. Тестов **1083 → 1102**,
|
||
удалённых имён **НОЛЬ** (пофамильный дифф).
|
||
- Все ШЕСТЬ каталожных мутаций пере-посажены и **все шесть КРАСНЫЕ**.
|
||
- Четыре обязательства `D39.150 п.1` исполнены ВСЕ, включая четвёртое: обещание точности снято из
|
||
`status.go`, шапка `reprice.go` заявляет обратное прежнему.
|
||
- Инвариант направления ошибки держится по построению: в `repriceCheckpoint` цена только повышается —
|
||
пол по ответившей модели, подъём до сегодняшней.
|
||
- Правки существующих тестов проверены на МОТИВ: подгонки нет, все стали СИЛЬНЕЕ (проверка трёх полей
|
||
→ проверка всей структуры; добавленное утверждение про basis).
|
||
- Границы соблюдены: схема, манифест, контракт не двинуты; `runevents/` тронут только комментариями;
|
||
два новых поля `status --json` аддитивны — платформа декодирует аллоулист.
|
||
- Кратность оценки `$0`-строки названа честно комментарием и ратифицирована «round up when it cannot
|
||
be known» — приёмка это принимает.
|
||
|
||
## Мандат самопроверки — исполнением
|
||
|
||
1. **Каждый пункт Б1–Б9 проверяется своей командой**, и в отчёте стоит её выход цитатой, а не пересказ.
|
||
2. **Мутации Б3 сажаются на копии дерева** (`cp -a backend /tmp/…`) и предъявляются КРАСНЫМИ.
|
||
Посадка, не покрасневшая, — находка, а не провал: так и пиши.
|
||
3. **Отдельный проход по своей готовой работе** свежим контекстом. Субагенты разрешены явно, модель
|
||
задавай ИМЕНЕМ и знай, сколько их работает.
|
||
4. ⚠ **«Прошёл, находок нет» отчётом НЕ является**: ноль находок обосновывается тем, ЧТО искалось и
|
||
какими командами.
|
||
|
||
## Отчёт
|
||
|
||
В свою секцию `docs/PROGRESS.md`. Пройди **по каждому** пункту Б1–Б9 и отчитайся по каждому — эта тема
|
||
в смене проваливалась несколько раз на том, что исполнялся первый пункт, а нумерованный остаток
|
||
бросался. Каждое число — с командой получения, которая его печатает; приёмка пере-ранит.
|
||
Обязательная секция **«Что НЕ удалось и что НЕ проверено»** — третий исход законен.
|
||
Конфликт этого наряда с кодом — **пинг**, не интерпретация: наряд писан по дереву индекса, код первичен.
|
||
|
||
## Прямой канал
|
||
|
||
Механизм — `CLAUDE.md`, «Связь между сессиями»: свой блок в `/tmp/textmachine-channel`, сверка с
|
||
`ListAgents` перед отправкой; канала нет — нормальный случай, сессии подряд не опрашивать.
|