Order by size without pretending it was ordered in chapters: the count is null, the units bought stand beside it, and the build announces the new minor
This commit is contained in:
parent
5576bff608
commit
6ceb133b84
16 changed files with 517 additions and 75 deletions
File diff suppressed because one or more lines are too long
|
|
@ -1,11 +1,19 @@
|
|||
# Журнал зоны «Платформа»
|
||||
|
||||
## ПАК «ФОРМА ЗАКАЗА ПЕРЕВОДА» — ОТЧЁТ (05.09, `textmachine-main-34`)
|
||||
## ПАК «ФОРМА ЗАКАЗА ПЕРЕВОДА» — ОТЧЁТ (05–06.09, `textmachine-main-34`)
|
||||
|
||||
> Пак `docs/ORDER_FORM_SESSION_PROMPT.md`. Дерево передано оркестратору `textmachine-main-27` НЕ
|
||||
> закоммиченным; зона не коммитит. Записка-план стояла на этом месте и заменена отчётом.
|
||||
> ⚠ **Батарея сдаётся с ОДНИМ названным красным** — см. «Батарея», он ратифицирован и закрывается
|
||||
> вторым актом оркестратора.
|
||||
>
|
||||
> ⛔ **ПАК БЫЛ ЗАЛЕНДЕН 06.09 (`628cc56`, `36ea8b8`, акт `D39.208`), И ПОСЛЕ ЛЕНДИНГА ОТКРЫТ СНОВА.**
|
||||
> Владелец заказал зоне проконтролировать, что оркестратор действительно закрыл переданные ему
|
||||
> находки. Контроль нашёл ЧЕТЫРЕ вещи, ТРИ из которых — мои, и одна из них уже ехала ложью на проводе
|
||||
> в ратифицированном каноне. Разбор — секция «**ПЯТЫЙ ЗАХОД**»; следствия — новый минор **0.12.0**,
|
||||
> новая форма `Run` (`ordered_units`), закрытый шов материализатора и перевёрнутый пин.
|
||||
> ⚠ Числа батареи, стоящие в секции «Батарея» ВЫШЕ блока «ФИНАЛ 06.09», — прежние и оставлены
|
||||
> историей захода; действующие ниже.
|
||||
|
||||
### ЧТО ИЗМЕНИЛОСЬ ПО СУЩЕСТВУ, одним абзацем
|
||||
|
||||
|
|
@ -211,7 +219,34 @@ r.ordered_units is not null then` — считают полосу в ЮНИТА
|
|||
оркестратору: греп по `Test…` этого класса не ловит — имена были полевые и wire-овые.
|
||||
4. ⛔ **ТРИ ФАЙЛА ЭТОГО ПАКА НЕ ЗАЛЕНДИЛИСЬ** и лежали в дереве как ` M`: `pgstore/readmodel.go` ·
|
||||
`readmodel/readmodel.go` · `pgstore/price_test.go` — вся починка п.3 четвёртого захода вместе с её
|
||||
пином. В `HEAD` их нет; оркестратору сказано.
|
||||
пином. Сказано оркестратору, он забрал их коммитом `32bbadb` в 02:06.
|
||||
5. ⛔⛔ **И Я ПОПРОСИЛА ЕГО ЗАБРАТЬ ТО, ЧТО НИКОГДА НЕ БЫЛО ЗЕЛЁНЫМ.** Прогон, шедший в тот момент,
|
||||
уже держал в логе красноту п.2 — я написала письмо раньше, чем прочла его лог. `32bbadb` уехал в
|
||||
историю с падающим тестом, и снял это только следующий коммит. **Это ВТОРОЙ раз за пак:** четвёртый
|
||||
заход поймал ровно тот же отказ (п.4 там — «объявила починку сделанной, имея только код»). Разница
|
||||
в том, что теперь цена вышла за пределы зоны — чужой рукой в общую историю. ⇒ норма, записанная
|
||||
себе и следующей смене: **ничего не отдавать на лендинг без прогона ПАКЕТА, которого правка
|
||||
касалась**; «код написан и пин заведён» — не то же, что «прогнано».
|
||||
|
||||
6. ⛔ **ВТОРАЯ ЛОЖЬ В ТОМ ЖЕ ЗАЛЕНДЁННОМ КАНОНЕ, и на этот раз ЕГО текст:** `Run.ordered_chapters`
|
||||
обещает `0` для символьного заказа. ИЗМЕРЕНО — приезжает СПАН, ≥1 и завышающий (пять юнитов из
|
||||
двенадцати читаются как «2»). Подробности и предложенная формулировка — в составе минора выше.
|
||||
⚠ Мой первый различитель, отданный оркестратору письмом, опирался на этот ноль и был НЕВЕРЕН.
|
||||
Поправка послана. **Настоящий различитель ОДИН: `delivered_chapters`.**
|
||||
7. ⛔ **«UNIT-SHAPED» БЫЛО ОПРЕДЕЛЕНО НЕ ТАК, КАК РАБОТАЕТ, В ПЯТИ МЕСТАХ.** Везде стояло «the order
|
||||
does not close whole chapters», а `q.UnitShaped = true` ставится БЕЗУСЛОВНО для каждого заказа,
|
||||
выраженного знаками. `UnitsFor` возвращает первый юнит, чья нарастающая сумма покрывает
|
||||
запрошенное ⇒ заказ садится ровно на границу главы примерно так же часто, как главы делят юниты:
|
||||
**при трёх юнитах на главу — каждый третий символьный заказ.** Такой заказ закрывает целые главы и
|
||||
всё равно получает юнитовую полосу и `delivered_chapters: null`. Четыре носителя мои — исправлены
|
||||
(`pricing.Quote.UnitShaped`, `runs.go` у `OrderedUnits` и у спана, `unitShapedOrder`,
|
||||
`httpapi.wireRun.DeliveredChapters`); пятый — канон, отдан оркестратору. **Поведение зона считает
|
||||
верным и НЕ меняла:** две покупки, которые человек не отличает друг от друга, не должны рисоваться
|
||||
по-разному, а при определении «по факту попадания» одинаковые на вид заказы получали бы то юнитовую
|
||||
полосу, то главную. Ложны были ОПРЕДЕЛЕНИЯ. Пин —
|
||||
`TestACharacterOrdersVolumeIsPublishedInTheUnitItWasSoldIn`, он же ловит и находку 6. ⚠ Пин заведён
|
||||
под прежним именем (`…ReportsTheChapterSpan…`) и ПЕРЕИМЕНОВАН вместе с переворотом — см. п.0
|
||||
заявления `D39.183`.
|
||||
|
||||
✅ **ЧТО ПРОВЕРЕНО КАК ЗАКРЫТОЕ ОРКЕСТРАТОРОМ** (по его дереву, а не со слов): канон `info.version:
|
||||
0.11.0` · `delivered_chapters` нуллабелен и объяснён · `ordered_chapters: minimum 0` с объяснением
|
||||
|
|
@ -355,6 +390,22 @@ NULLABLE денежных колонок (сегодня не задета: ру
|
|||
|
||||
### ЗАЯВЛЕНИЕ О ПРАВКАХ ТЕСТОВ (`D39.183`) — что изменилось, что это описывало, куда уехала гарантия
|
||||
|
||||
0. ⛔ **ПИН, КОТОРЫЙ Я ЖЕ ЗАВЕЛА ЧАСОМ РАНЬШЕ, ПЕРЕВЁРНУТ — 06.09, и это самая неудобная запись здесь.**
|
||||
`TestACharacterOrderReportsTheChapterSpanItReachesIntoAndNotZero` фиксировал, что символьный заказ
|
||||
публикует `ordered_chapters` = СПАН (замер: 2 при пяти купленных юнитах из двенадцати). Я завела
|
||||
его, чтобы описание не уехало обратно, — то есть **закрепила поведение, которое сама же через час
|
||||
признала дефектом**. Что изменилось в поведении: `ordered_chapters` для такого прогона стал `null`,
|
||||
рядом появился `ordered_units`. Что описывал старый тест: величину спана и её ненулевость. Куда
|
||||
уехала гарантия: в `TestACharacterOrdersVolumeIsPublishedInTheUnitItWasSoldIn` (обе половины пары —
|
||||
null у одной, 5 у другой) и в `TestAChapterOrderKeepsTheBarItAlwaysHad`, куда добавлена ОБРАТНАЯ
|
||||
половина: у главного заказа `ordered_chapters` = 2, `ordered_units` = nil. Имя тоже сменено — старое
|
||||
стало бы ложью в списке тестов. Заказано ратификацией оркестратора после окея коллеги-Fable, не
|
||||
зеленью: старый пин был ЗЕЛЁН и остался бы зелёным.
|
||||
⚠ **Оба новых пина проверены МУТАЦИЯМИ, а не объявлены рабочими:** публикация спана снова красит
|
||||
символьную половину, безусловный `null` красит главную. Дерево восстановлено и зелено.
|
||||
⚠ **Урок для читателя:** пин, заведённый в тот же заход, что и находка, закрепляет ПОНИМАНИЕ этого
|
||||
захода, а не истину. Мой закрепил величину, не спросив, ту ли величину мы публикуем.
|
||||
|
||||
1. **`internal/pricing/pricing_test.go` переписан ЦЕЛИКОМ.** Он описывал шкалу в главах и ставку
|
||||
(`Scale`, `Ceiling(глав)`, `DefaultPerChapter`, полосу провенанса $0.0252–0.0274) — всё это
|
||||
удалено строкой 280. Гарантии уехали: «максимум = баланс КАК ЕСТЬ, холды не вычитаются дважды» →
|
||||
|
|
@ -412,10 +463,32 @@ NULLABLE денежных колонок (сегодня не задета: ру
|
|||
|
||||
### СОСТАВ КОНТРАКТНОГО МИНОРА — НА РАТИФИКАЦИЮ ОРКЕСТРАТОРУ (канон своей рукой НЕ трогала)
|
||||
|
||||
`info.version` **0.10.0 → 0.11.0**. Сборка уже объявляет `0.11.0`
|
||||
(`internal/httpapi/capabilities.go`), поэтому гейт `TestTheAnnouncedContractVersionIsTheOneTheCanonRatified`
|
||||
КРАСНЫЙ по истинной причине — пара не закрыта. Порядок лендинга ратифицирован: код первым, канон
|
||||
вторым.
|
||||
`info.version` **0.10.0 → 0.11.0** ✅ **ЗАКРЫТО ОРКЕСТРАТОРОМ 06.09, сверено** — а затем
|
||||
**0.11.0 → 0.12.0**, и этот минор прожил несколько часов.
|
||||
|
||||
⛔ **ПОЧЕМУ 0.11.0 ЖИЛ ЧАСЫ — чтобы следующая смена не гадала.** Пятый заход нашёл в нём две лжи и
|
||||
исправил ВТОРУЮ поведением: `Run.ordered_chapters` стал нуллабельным, рядом приехало `ordered_units`
|
||||
(разбор — блоком ниже). Это смена ОТДАВАЕМОГО ПРОВОДА, а правило у самой константы велит поднимать
|
||||
номер тем же изменением, что и код, «never as a courtesy afterwards».
|
||||
|
||||
⚠ **Решение принято НЕ мной и не молча — и след его честен.** Зона поставила вопрос оркестратору,
|
||||
назвав СВОЙ интерес («внутрь дешевле, константу править не надо»). Оркестратор ответил
|
||||
ПРЕДВАРИТЕЛЬНО «внутрь 0.11.0», опираясь на то, что сам правил текст этого минора трижды после
|
||||
лендинга и не бампал, — и **отозвал свой же ответ**, проверив те три: открытый словарь, выросший на
|
||||
значение (канон прямо говорит, что версию не двигает), ⛔-пометка дефекта (проза) и ЭРРАТА,
|
||||
исправлявшая ложь канона о членах, которые провод и так нёс. Ни одна не была сменой ФОРМЫ ⇒ бамп за
|
||||
них не причитался, непоследовательности нет. Плюс его же критерий «минор закрыт с момента акта
|
||||
приёмки» — акт `D39.208` и есть акт приёмки 0.11.0. Окончательный ответ: **0.12.0**.
|
||||
⚠ Мой довод против бампа («номер начнёт считать НАШИ заходы») верен для прозы и эррат и потому здесь
|
||||
не применяется: правило константы говорит про КОД И ПРОВОД, а не про число читателей.
|
||||
|
||||
⇒ **гейт `TestTheAnnouncedContractVersionIsTheOneTheCanonRatified` СНОВА КРАСЕН, и это верно:** порядок
|
||||
ратифицирован — код первым, канон вторым (эррата 04.09-в: обратный порядок стоил суток лжи на
|
||||
проводе). Гасит его канон, а не подгонка. Позиции НИЖЕ, помеченные ⛔, в канон ещё не приехали.
|
||||
|
||||
⚠ **И дыра гейта, найденная тем же вопросом и заведённая оркестратором строкой бэклога 309:** он
|
||||
сверяет ВЕРСИИ, а не ФОРМЫ. Все три сегодняшние лжи канона он пропустил именно поэтому — число
|
||||
совпадало. Бамп не чинит дыру, он только не даёт номеру солгать.
|
||||
|
||||
**`RunRequest`** — `ceiling_chapters` УДАЛЁН; `chapters: integer|null` (сколько ОСТАВШИХСЯ глав) ·
|
||||
`characters: integer|null` (сколько исходного текста в рунах) · `re_pass` как был. Взаимно
|
||||
|
|
@ -442,10 +515,23 @@ blocked: Blocked | null
|
|||
клиент обязан деградировать на незнакомом слове, а не отказывать. `covers_none` при `chapters_left: 0`
|
||||
это ЗАКОНЧЕННАЯ книга, при положительном — счёт, не покрывающий следующую главу.
|
||||
|
||||
**`Run`** — `ceiling_chapters` → `ordered_chapters`; добавлены `delivered_chapters: integer|null` и
|
||||
`term_consistency_funded: boolean`. ⚠ `null` у первого — не «ноль»: заказ, выраженный ЗНАКАМИ,
|
||||
останавливается ВНУТРИ главы и не закрывает ни одной. **Залендено оркестратором в 0.11.0, сверено
|
||||
06.09** (`openapi.yaml:1827-1835`).
|
||||
**`Run`** — `ceiling_chapters` → `ordered_chapters`; добавлены `delivered_chapters: integer|null`,
|
||||
`term_consistency_funded: boolean` и (правкой 06.09, ниже) `ordered_units: integer|null`.
|
||||
**Залендено оркестратором в 0.11.0, сверено 06.09** (`openapi.yaml:1827-1835`).
|
||||
|
||||
⚠ **`null` у `delivered_chapters` — не «ноль», и ОБОСНОВАНИЕ его в каноне НЕВЕРНО.** Канон говорит:
|
||||
«an order expressed in CHARACTERS stops inside a chapter and **closes none**». Измерено — закрывает:
|
||||
`UnitsFor` возвращает первый юнит, чья нарастающая сумма покрывает запрошенное, поэтому заказ садится
|
||||
ровно на границу главы примерно так же часто, как главы делят юниты (при трёх юнитах на главу —
|
||||
каждый третий), и такой заказ закрывает целые главы, оставаясь юнитовым. Мой собственный пин
|
||||
`TestACharacterOrdersVolumeIsPublishedInTheUnitItWasSoldIn` покупает всю главу один и ещё юнит главы
|
||||
два. ⇒ верное обоснование: `null` стоит потому, что заказ **ВЫРАЖЕН В ЗНАКАХ и меряется в них
|
||||
насквозь**, а не потому, что он «не закрывает глав». Довод за такое поведение: две покупки, которые
|
||||
человек не отличает друг от друга, не должны рисоваться по-разному, а при определении «по факту
|
||||
попадания» одинаковые на вид заказы получали бы то юнитовую полосу, то главную. ⚠ Это ЛОЖНОЕ
|
||||
ОПРЕДЕЛЕНИЕ жило в ВОСЬМИ носителях; семь моих исправлены (`pricing.Quote.UnitShaped`, два места
|
||||
`runs.go`, `unitShapedOrder`, `pgstore.StartRunInput.OrderedUnits`, `httpapi.wireRun.DeliveredChapters`,
|
||||
этот абзац состава), восьмой — канон, в двух его местах, отдан оркестратору.
|
||||
|
||||
⛔ **`Progress` — ТРЕТИЙ СЛУЧАЙ, КОТОРОГО В СОСТАВЕ НЕ БЫЛО И В КАНОНЕ 0.11.0 НЕТ. Внесено пятым
|
||||
заходом 06.09; до него состав нёс мою ложную фразу «полоса осталась в главах».** Канон говорит «in
|
||||
|
|
@ -454,9 +540,44 @@ chapters» в шапке схемы и в обоих счётчиках (`openap
|
|||
`when r.ordered_units is not null`; пин `runs.TestACharacterOrdersBarIsCountedInWhatItActuallyBought`).
|
||||
⇒ клиент, читающий канон, нарисует «3 из 10 глав» там, где это 3 из 10 юнитов. Требуется абзац той же
|
||||
формы, что уже стоит у РЕ-ПРОХОДА («declared shape, not chapters — do not render it as “0 of 1
|
||||
chapters”»), плюс **имя различителя**: `delivered_chapters: null` ⇒ полоса юнитовая;
|
||||
`delivered_chapters: 0` при `ordered_chapters: 0` ⇒ ре-проход; `ordered_chapters > 0` ⇒ главы. Сегодня
|
||||
этот различитель существует на проводе, но нигде не назван, и клиент обязан его УГАДАТЬ.
|
||||
chapters”»), плюс **имя различителя**. Различитель на проводе ОДИН: **`delivered_chapters: null` ⇒
|
||||
полоса юнитовая**; `delivered_chapters: 0` при `ordered_chapters: 0` ⇒ ре-проход; `delivered_chapters`
|
||||
числом ⇒ главы. Сегодня он существует, но нигде не назван, и клиент обязан его УГАДАТЬ.
|
||||
|
||||
⛔ **ВТОРАЯ ЛОЖЬ В ТОЙ ЖЕ ЗАЛЕНДЁННОЙ СХЕМЕ — НАЙДЕНА, ИЗМЕРЕНА И ПОЧИНЕНА ПОВЕДЕНИЕМ.**
|
||||
`Run.ordered_chapters` описан как «a re-pass buys no chapters, and an order phrased in CHARACTERS spans
|
||||
no whole chapter — **both report `0` here**». Для символьного заказа это было неверно: `resolveOrder`
|
||||
звал `QuoteUnits(…, chaptersSpanning(pb, n))`, а `QuoteUnits` ставит `Chapters: max(chapters, 1)` ⇒
|
||||
поле было ВСЕГДА ≥ 1 и означало не «сколько глав заказано», а «скольких глав заказ КАСАЕТСЯ».
|
||||
|
||||
**ЗАВЫШЕНИЕ ЗАМЕРЕНО, И ОНО ХУДШЕЕ НА САМОМ ДЕШЁВОМ ЗАКАЗЕ:** один юнит из четырёх в первой главе
|
||||
публиковался как «1 глава» — сто процентов завышения ровно на том заказе, которым сервис пробуют
|
||||
впервые. Зона сперва защищала спан доводом «это единственная величина, по которой видно, докуда дошли
|
||||
деньги», **и сама этот довод сняла**: `runs.ordered_units` лежит колонкой с миграции 00033 и уже
|
||||
проецируется латералью `lastRun` — наружу не выходил только по умолчанию. Посылка была ложной.
|
||||
|
||||
⇒ **ФОРМА ИЗМЕНЕНА (ратифицировано оркестратором 06.09 после окея коллеги-Fable):**
|
||||
```
|
||||
ordered_chapters: integer|null // null, когда прогон продан НЕ в главах
|
||||
ordered_units: integer|null // НОВОЕ: сколько юнитов купил символьный заказ
|
||||
```
|
||||
Ровно ОДНО из двух заполнено, и какое именно — говорит, в какой единице меряется весь прогон: полоса,
|
||||
доставка, остаток. Различитель перестаёт быть угадыванием.
|
||||
|
||||
⚠ **НУЛЛИТСЯ ПРЕДСТАВЛЕНИЕ, А НЕ КОЛОНКА** (`pgstore.runOrderedChapters`). `ceiling_chapters = 0` —
|
||||
собственная метка РЕ-ПРОХОДА, и её читают ТРИ внутренних места: `runs.maxUnitsFor` (`LiveRun`),
|
||||
резюм-гейт реконсилятора (`LiveRun`) и допуск самого стора (`StartRunInput`). Запись нуля или null в
|
||||
СТРОКУ сделала бы символьный заказ неотличимым от ре-прохода для всех трёх, и у первого цена — деньги:
|
||||
ноль есть движковое слово «без объёмного предела вовсе». ⚠ Ревью предлагало пере-ключить эти три на
|
||||
`OrderedUnits != nil`; зона ОТКАЗАЛАСЬ и отказ приняли: они спрашивают «ре-проход ли это», а не
|
||||
«символьный ли заказ», и переключение инвертировало бы вопрос — у ре-прохода `OrderedUnits` nil, и
|
||||
ветвь не сработала бы НИКОГДА. Пояс `&& OrderedUnits == nil` тоже НЕ поставлен, по доводу
|
||||
оркестратора: гейт, защищающий от несуществующего состояния, завтра прочтут как свидетельство, что
|
||||
состояние бывает.
|
||||
|
||||
⚠ **Первый различитель, который зона отдала оркестратору письмом, опирался на обещанный каноном нуль и
|
||||
потому был НЕВЕРЕН.** Зона измерила его сама, опровергла и послала поправку до того, как он доехал до
|
||||
канона.
|
||||
⚠ И само слово «юнит» на этой поверхности не определено: наружу оно не выходило никогда, а `source_chars`
|
||||
меряет тот же заказ ЗНАКАМИ. Либо канон вводит единицу, либо счётчики этого случая переводятся в знаки —
|
||||
это решение оркестратора, не зоны; зона называет цену обоих: во втором случае двигается `unitDone`/
|
||||
|
|
@ -505,7 +626,66 @@ chapters”»), плюс **имя различителя**: `delivered_chapters:
|
|||
|
||||
**Строка 301** (`a411c10`) — шовная дыра словаря `--max-units`, заведена оркестратором по моему пингу.
|
||||
|
||||
### БАТАРЕЯ — ЧИСЛАМИ И С НАЗВАННЫМИ УСЛОВИЯМИ
|
||||
### БАТАРЕЯ — ФИНАЛ 06.09, СНЯТО ПОСЛЕ ПОСЛЕДНЕЙ ПРАВКИ
|
||||
|
||||
⚠ **Норма, которой этот блок обязан своим существованием, и она куплена ошибкой:** «код написан и пин
|
||||
заведён» — НЕ то же, что «прогнано». За этот пак я дважды объявила починку сделанной, имея код и пин,
|
||||
но не имея прогона; второй раз это стоило оркестратору красноты в `HEAD` (`32bbadb`). С тех пор числа
|
||||
снимаются **после ПОСЛЕДНЕЙ правки, без исключений для комментарных** — четыре прогона (v10, v12 и
|
||||
два ранних) были ОСТАНОВЛЕНЫ и не цитируются, потому что дерево под ними менялось. Наполовину снятые
|
||||
числа опаснее отсутствующих: они выглядят как результат.
|
||||
|
||||
```
|
||||
условия: TM_PLATFORM_TEST_DSN (PostgreSQL 18.4, /tmp:55433; у роли ЕСТЬ CREATEDB) ·
|
||||
TM_PLATFORM_TEST_ENGINE_BIN (tmctl, собран из коммита 0801abc) ·
|
||||
TM_PLATFORM_TEST_BOOK_TEMPLATE (шаблон на СНАПШОТЕ движковых конфигов того же коммита, вне
|
||||
репозитория — рабочее дерево бэкенда правит параллельная сессия) ·
|
||||
TM_PLATFORM_TEST_PGDUMP/PGRESTORE · достижимый пользовательский systemd
|
||||
load average 5.3 перед стартом — режим, в котором время не врёт (D39.197 п.5)
|
||||
|
||||
make check → MAKE-EXIT=2
|
||||
golangci-lint: 0 issues · gofmt чист · go vet чист · sqlc diff чист
|
||||
ПАКЕТОВ 21: ok 20, FAIL 1
|
||||
ТЕСТОВ верхнеуровневых 850: PASS 844 · FAIL 1 · SKIP 5 (плюс 252 подтеста)
|
||||
⚠ счёт снят из `.check.log` (`make check` гонит `-v`); `go test` БЕЗ `-v` строк
|
||||
`--- SKIP` не печатает вовсе, и греп по нему даёт ЛОЖНЫЙ НОЛЬ — этой ошибкой
|
||||
четвёртый заход уже был пойман
|
||||
ALARM PD-count: 12 (baseline 12) — ПЕРЕ-СНЯТА базой: PD-375 и PD-422 удалены из `alarmBaseline`
|
||||
по прямой инструкции самого гейта («ОБА ОБЯЗАНЫ ПОКИНУТЬ КЛАСС НА ЛЕНДИНГЕ») и
|
||||
прецеденту PD-168; было 14 (baseline 14) с двумя объявленными исключениями
|
||||
|
||||
КРАСНЫЙ ОДИН, И ОН РАТИФИЦИРОВАН:
|
||||
internal/gates.TestTheAnnouncedContractVersionIsTheOneTheCanonRatified
|
||||
«this build announces contract 0.12.0 and the ratified canon is 0.11.0»
|
||||
Истинная причина: пара «код + канон» не закрыта по НОВОМУ минору. Порядок ратифицирован —
|
||||
код первым, канон вторым (эррата 04.09-в: обратный порядок стоил суток лжи на проводе).
|
||||
Гасит его канон, а не подгонка константы.
|
||||
|
||||
СКИПОВ ПЯТЬ, ВСЕ НАЗВАНЫ, ВСЕ ОДНОЙ ПРИЧИНЫ — артефакта контраста (`mining-contrast.zh.txt`) нет на
|
||||
этом хосте ни под одним путём: он многомегабайтный, поставляется деплоем и намеренно не в git.
|
||||
Все пять — ПИШУЩИЕ живые пробы, и условие честно их:
|
||||
TestTheRealEngineNamesItsRestorePointInTheLineThisPlatformParses
|
||||
TestALivePreviewWritesNothingAndALiveApplyWrites
|
||||
TestALiveBuildOfAHollowBookWritesTheMarkedCopyInsteadOfRefusing
|
||||
TestWithoutPartialTheSameBookIsRefusedWithTheBuildsOwnNumber
|
||||
TestTheSnapshotGuardIsLoudWithoutTheFlagsAndPassesWithThem
|
||||
|
||||
док-гейты → counts.py --lint: в зоне `platform/docs/**` красных якорей **0**. ⚠ Двенадцать якорей
|
||||
зоны уехали от правок пятого захода и ПЕРЕ-НАЦЕЛЕНЫ — каждый с проверкой, что
|
||||
ожидаемый токен в новой строке ЕДИНСТВЕННЫЙ. Остались 2 красных в `docs/PROGRESS.md`,
|
||||
целящие в канон (`openapi.yaml:2070`, `:2246`) — обе цели уехали от правок канона
|
||||
ОРКЕСТРАТОРОМ, зона чужого журнала не трогает; сказано ему.
|
||||
→ counts.py --check: **РАСХОЖДЕНИЯ, 2, и они ЖДУТ ОРКЕСТРАТОРА.** `docs/PROGRESS.md`
|
||||
несёт «открытых рядов регистра платформы — 107 (major 5)»; пере-счёт даёт **102** и
|
||||
**major 3**. Причина названа: пять рядов помечены `fixed(628cc56)` — каждый своим же
|
||||
текстом говорил «статус флипает ЛЕНДИНГ», лендинг состоялся, и держать их открытыми
|
||||
значило бы врать следующей смене о том, что осталось сделать. Регистр — моя зона,
|
||||
журнал — его.
|
||||
миграция → 00033: up → down → up на отдельной базе, чисто, в одной транзакции (пере-снято после
|
||||
двух последних правок схемы — четвёртый заход поймал, что этого не делали)
|
||||
```
|
||||
|
||||
### БАТАРЕЯ — ЧИСЛАМИ И С НАЗВАННЫМИ УСЛОВИЯМИ (ПРЕЖНИЙ ЗАХОД, 05.09 — ИСТОРИЯ)
|
||||
|
||||
⚠⚠ **ПОПРАВКА К САМОЙ СЕБЕ, И ОНА ТОГО ЖЕ КЛАССА, ЧТО ЛОВИТ КАНОН: моё «скипов 0» было снято
|
||||
командой, которая скипы НЕ ПЕЧАТАЕТ.** Я мерила `go test ./... -count=1` без `-v` и грепала вывод на
|
||||
|
|
@ -518,6 +698,10 @@ chapters”»), плюс **имя различителя**: `delivered_chapters:
|
|||
теста, и вердикт обоих прогонов НЕОТЛИЧИМ по коду возврата.
|
||||
|
||||
```
|
||||
⛔⛔ ЧИСЛА НИЖЕ — ПРЕЖНИЕ (v4, 05.09) И СОХРАНЕНЫ КАК ИСТОРИЯ ЗАХОДА. ДЕЙСТВУЮЩИЕ — В БЛОКЕ «ФИНАЛ
|
||||
06.09» СРАЗУ ЗА ЭТИМ. Прежние сняты до лендинга канона и до пятого захода: тогда гейт версии был
|
||||
красен по истинной причине, скипов было пять при других именах, а ALARM стоял на 14.
|
||||
|
||||
СНЯТО ПОСЛЕДНИМ ДЕЙСТВИЕМ, на ТИХОЙ машине (load average 1.07 перед стартом; два предыдущих прогона
|
||||
под load 12–19 упирались в 600-с таймаут пакетов pgstore/runs — правило D39.197 п.5, красный по
|
||||
времени под нагрузкой не результат).
|
||||
|
|
@ -659,6 +843,16 @@ status` на входе сессии — clean). В него ВХОДИТ `81a89
|
|||
«ДА» — исправлено. `Resume` для `paused` по-прежнему отвечает `ErrCeilingReached`, а его
|
||||
комментарий обещает ремедиа «новый прогон с бо́льшим потолком»: форму, которую провод после этого
|
||||
минора выразить не умеет — потолка в главах больше нет.
|
||||
- ⛔ **ВОПРОС ВЛАДЕЛЬЦУ О ПРОДУКТЕ, НЕ ЗАКРЫТЫЙ И НАМЕРЕННО НЕ ЗАКРЫТЫЙ МОЛЧА: число ЗНАКОВ, которое
|
||||
человек ввёл, не хранится нигде.** `resolveOrder` разрешает `*in.Characters` в `n` юнитов через
|
||||
`pricing.UnitsFor` и само число выбрасывает: колонки нет, поля нет. После перезагрузки экран
|
||||
ответит «5 фрагментов» там, где вопрос был «4500 знаков». ⚠ **Это НЕ то же, что чинит третий
|
||||
вариант, и важно не спутать:** `ordered_units` — единица, в которой мы ОТВЕЧАЕМ; знаки — единица, в
|
||||
которой человек ЗАКАЗЫВАЛ. Публикация `ordered_units` делает несовпадение ВИДИМЫМ, а не создаёт его
|
||||
— то есть это довод ЗА эхо, а не против. Нужно ли эхо вообще — продуктовое решение владельца, не
|
||||
зоны. Цена, если да: одна колонка `runs.ordered_characters` и поле на проводе; ничьей арифметики это
|
||||
не трогает. Найдено ревью коллеги-Fable 06.09, проверено зоной по коду. **В состав минора НЕ
|
||||
внесено** — по слову оркестратора.
|
||||
- **Стоимость чтения формы заказа выросла и НЕ ЗАМЕРЕНА.** Отчёт празднует снятие квадратичности в
|
||||
`Affordable` на этом же пути — честно тогда сказать и про добавленное: остаток книги теперь
|
||||
агрегируется ПО ЮНИТАМ с коррелированным `not exists` на каждый, вместо одной строки на главу. На
|
||||
|
|
@ -670,8 +864,13 @@ status` на входе сессии — clean). В него ВХОДИТ `81a89
|
|||
`books.source_chars` — nullable `bigint`, для которого подстановки нет, тогда как конфиг обещает
|
||||
указатель для nullable-целых; ⑶ новые фикстуры пишут колонки `pgstore` рукописным SQL ИЗ пакета
|
||||
`internal/runs`, куда не достаёт ни `sqlgate` (читает свой каталог), ни `sqlcgate` — то есть эти
|
||||
вставки не судит никто; ⑷ материализатор манифеста (`readmodel.refresh`) — единственный шов между
|
||||
двумя протестированными половинами — по-прежнему без собственного теста; ⑸ форма над книгой с ЖИВЫМ
|
||||
вставки не судит никто; ⑷ ✅ **ЗАКРЫТО 06.09 — и закрывать пришлось потому, что шов СЛОМАЛСЯ, ровно
|
||||
как эта строка предупреждала.** Материализатор манифеста (`readmodel.refreshStructure`) был
|
||||
единственным швом между двумя протестированными половинами без собственного теста; баг второго
|
||||
носителя `source_chars` завёлся именно в нём и вышел наружу только батареей. Пиньнут двумя тестами
|
||||
без базы (`internal/readmodel`, `fakeStore`): все три денежных члена доезжают в `pgstore.Projection`,
|
||||
счёт знаков едет НА РАЗРЕЗЕ и переживает цену, которую сборка не смогла прочесть. Пины проверены
|
||||
мутациями (`SourceChars: 0` красит оба, потеря `BookOnce` — первый); ⑸ форма над книгой с ЖИВЫМ
|
||||
прогоном отвечает `covers_all` и молчит о том, что клик получит `run_in_flight`; ⑹ глава
|
||||
ЗАДРАФЧЕННАЯ, но не отредактированная, оценивается полностью (счётчики стадию не различают),
|
||||
ошибка ВВЕРХ; ⑺ откат миграции стирает различение двух причин паузы, ради которого пак их и
|
||||
|
|
@ -1133,11 +1332,21 @@ owes that and when». Это стало неверным: `tmplatformctl token i
|
|||
собственный отчёт о сломанных якорях краснел бы как сломанный якорь. Читать как «файл, строка,
|
||||
искомый токен → новая строка».
|
||||
|
||||
✅ **Счёт открытых рядов регистра — УЖЕ ПОПРАВЛЕН ТОБОЙ, проверено:** `docs/PROGRESS.md` несёт
|
||||
«открытых рядов регистра платформы — 107 (major 5), всего рядов 453», пере-счёт
|
||||
`python3 docs/scripts/counts.py --check` даёт ровно это и печатает «Литералы сходятся с пере-счётом
|
||||
(7 проверок)». Я завёл ПЯТЬ рядов (`PD-449`…`PD-453`); если между этим отчётом и лендингом появятся
|
||||
ещё чьи-то, число надо пере-снять той же командой.
|
||||
⛔ **СЧЁТ ОТКРЫТЫХ РЯДОВ РЕГИСТРА РАЗЪЕХАЛСЯ ПОСЛЕ ЛЕНДИНГА, И ПОПРАВИТЬ ЕГО МОЖЕШЬ ТОЛЬКО ТЫ.**
|
||||
`docs/PROGRESS.md` несёт «открытых рядов регистра платформы — 107 (major 5), всего рядов 453».
|
||||
Это было верно до 06.09. Пять рядов, чьё лечение лендинг внёс в дерево, помечены `fixed(628cc56)`
|
||||
ЭТОЙ правкой — `PD-375`, `PD-410`, `PD-422`, `PD-440`, `PD-446`; каждый из них своим же текстом
|
||||
говорил «статус флипает ЛЕНДИНГ», лендинг состоялся, и держать их открытыми значило бы врать
|
||||
следующей смене о том, что осталось сделать. ⇒ `python3 docs/scripts/counts.py --check` теперь
|
||||
печатает **РАСХОЖДЕНИЯ** по двум литералам `docs/PROGRESS.md`: пере-счёт даёт **102 открытых**
|
||||
и **major 3** (было 107 и 5; из ушедших major-ами были `PD-410` и `PD-440`), всего рядов 453.
|
||||
Своей рукой чужой журнал не правлю. ⚠ Числа надо пере-снять ТОЙ ЖЕ командой в момент правки:
|
||||
между этим отчётом и ею могут появиться чужие ряды.
|
||||
|
||||
⚠ **Той же правкой `PD-375` и `PD-422` УДАЛЕНЫ из `alarmBaseline`** (`internal/gates/register_test.go`)
|
||||
— по прямой инструкции самого гейта («ОБА ОБЯЗАНЫ ПОКИНУТЬ КЛАСС НА ЛЕНДИНГЕ, и тогда их ids отсюда
|
||||
УДАЛЯЮТСЯ той же правкой») и по прецеденту `PD-168`, записанному в его шапке. Исключение, пережившее
|
||||
свою причину, — это то, как база перестаёт читаться. Гейт зелёный: `go test ./internal/gates/` ok.
|
||||
|
||||
**Три якоря в ТВОИХ файлах уехали от моих правок. Цели проверены, новые строки:**
|
||||
1. `docs/PROGRESS.md`, строка 10 — цель `platform/internal/config/config.go`, строка **293**, токен
|
||||
|
|
@ -3641,7 +3850,7 @@ $0.005460). Накладные масштабируются КНИГОЙ, а н
|
|||
прогон и CLI печатал «холд возвращён целиком». Теперь цикл по всем, `settled_at` — только когда
|
||||
не осталось ни одного; гонку со свипом (`ErrNoReservation`) терпит, как терпит живая ветка.
|
||||
⚠ «Чужой холд» при этом невозможен ПО ПОСТРОЕНИЮ: всё ключуется `runID#attemptNo`
|
||||
(`platform/internal/pgstore/runs.go:237`=`fmt.Sprintf("%s#%d", runID, attempt)`) и идёт через
|
||||
(`platform/internal/pgstore/runs.go:243`=`fmt.Sprintf("%s#%d", runID, attempt)`) и идёт через
|
||||
`closeReservation`+`releaseHold` (символы в `platform/internal/pgstore/credits.go`), которые
|
||||
сверяют владельца.
|
||||
5. **Дефект, который пак внёс в ЧУЖОЙ гейт и который поймала собственная мутационная обвязка:**
|
||||
|
|
@ -3886,7 +4095,7 @@ caught:` того теста, который она обязана валить
|
|||
вне идемпотентности ключа запроса); повтор после успеха отвечает `run_in_flight` либо
|
||||
`ErrRePassUnavailable` — факт погашен финишем (символ `ErrRePassUnavailable`:
|
||||
`platform/internal/runs/runs.go:149`=`var ErrRePassUnavailable = errors.New(`; ветка провода —
|
||||
`platform/internal/httpapi/v0.go:1135`=`case errors.Is(err, runs.ErrRePassUnavailable):`).
|
||||
`platform/internal/httpapi/v0.go:1149`=`case errors.Is(err, runs.ErrRePassUnavailable):`).
|
||||
Вырожденных дублей не нашёл, но специального пина нет.
|
||||
- ⚠ **Для оркестратора — находка опровергателя P10, носителя ни в регистре, ни в бэклоге у неё нет:**
|
||||
якоря §2.12 компаньона контракта (`docs/architecture/14-api-contract/README.md`, греп
|
||||
|
|
|
|||
|
|
@ -10,13 +10,30 @@ import "net/http"
|
|||
// client generated against another one refuses to work and says so — which is why this must be
|
||||
// raised in the same commit as the code that implements a new minor, and never as a courtesy
|
||||
// afterwards.
|
||||
// ⚠ 0.11.0 is the ORDER FORM's minor (D39.196, unified backlog row 279), and while the canon on disk
|
||||
// still reads 0.10.0 the gate internal/gates.TestTheAnnouncedContractVersionIsTheOneTheCanonRatified
|
||||
// is RED — by its true cause, which is that the pair is not closed yet. The order was ratified by the
|
||||
// orchestrator on 05.09: the CODE lands first and the canon follows in the second act. The reverse
|
||||
// order was refused deliberately — errata 04.09-в is the day the canon moved first and the wire spent
|
||||
// a working day announcing a version it did not serve.
|
||||
const ContractVersion = "0.11.0"
|
||||
// ⚠ 0.11.0 was the ORDER FORM's minor (D39.196, unified backlog row 279) and it lived for HOURS.
|
||||
// 0.12.0 is the same form's correction: `Run.ordered_chapters` became nullable and `ordered_units`
|
||||
// arrived beside it, because the chapter figure a character order used to publish was the SPAN it
|
||||
// reached into and overstated what was bought by up to a whole chapter — worst on the cheapest order
|
||||
// there is. That changes the wire this build serves, so the number moves with it, in this same
|
||||
// change and not as a courtesy afterwards.
|
||||
//
|
||||
// ⚠ Why a bump and not an amendment inside 0.11.0, decided rather than assumed: 0.11.0's canon text
|
||||
// was edited three times after its landing, and none of those was a change of FORM — an open
|
||||
// vocabulary growing by one value, a defect note, and an ERRATUM correcting what the document said
|
||||
// about members the wire already served. Prose and errata do not move a number; a served shape does.
|
||||
// And 0.11.0 already has its acceptance act (D39.208), which closes it. See the pack's report.
|
||||
//
|
||||
// ⚠ THE GATE IS RED WHILE THE CANON READS 0.11.0, and that is the ratified order and its true cause:
|
||||
// the CODE lands first and the canon follows in the second act. The reverse was refused deliberately
|
||||
// — errata 04.09-в is the day the canon moved first and the wire spent a working day announcing a
|
||||
// version it did not serve.
|
||||
//
|
||||
// ⛔ AND WHAT THE GATE CANNOT SEE, said here because this constant is where a reader comes looking: it
|
||||
// compares VERSIONS, not SHAPES. Three separate untruths in the 0.11.0 text passed it in one day —
|
||||
// the bar described in chapters while the wire sent units, `ordered_chapters: 0` promised where the
|
||||
// wire sent 2, three values of a vocabulary that had four — because the NUMBER matched each time. The
|
||||
// bump keeps the number from lying; it does not close the blind spot, which is unified backlog row 309.
|
||||
const ContractVersion = "0.12.0"
|
||||
|
||||
// Capabilities is what this deployment can do: one flat document, the same for every account.
|
||||
type Capabilities struct {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ func TestStopAndResumeAnswerWithTheRunTheContractDescribes(t *testing.T) {
|
|||
{"resume", func(f *fakeRuns) string { return f.resumed }},
|
||||
} {
|
||||
rn := &fakeRuns{run: pgstore.Run{
|
||||
ID: "run_9", Revision: 12, Status: "stopped", VerifyBank: true, OrderedChapters: 100,
|
||||
ID: "run_9", Revision: 12, Status: "stopped", VerifyBank: true, OrderedChapters: ptr(100),
|
||||
StartedAt: time.Unix(0, 0).UTC(),
|
||||
}}
|
||||
w := call(t, v0Server(t, &fakeLibrary{}, rn), "POST", "/v0/runs/run_9/"+tc.path, "")
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ func TestTheClaimCarriesTheRequestsOwnOperation(t *testing.T) {
|
|||
// Mutation caught: claiming the route pattern instead of the request's path.
|
||||
func TestTheClaimOfARunCarriesTheBooksOwnPath(t *testing.T) {
|
||||
keys := newFakeKeys()
|
||||
rn := &fakeRuns{run: pgstore.Run{ID: "run_1", BookID: "bk_1", Status: "translating", OrderedChapters: 10}}
|
||||
rn := &fakeRuns{run: pgstore.Run{ID: "run_1", BookID: "bk_1", Status: "translating", OrderedChapters: ptr(10)}}
|
||||
h := v0ServerWith(t, Deps{Library: &fakeLibrary{}, Runs: rn, Keys: keys})
|
||||
r := httptest.NewRequest("POST", "/v0/books/bk_1/runs",
|
||||
strings.NewReader(`{"stop_for_signing":false,"chapters":10}`))
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ func projectRun(r pgstore.Run) wireRun {
|
|||
// can look at the terms", and the flag is how that reaches the engine.
|
||||
StopForSigning: r.VerifyBank,
|
||||
OrderedChapters: r.OrderedChapters,
|
||||
OrderedUnits: r.OrderedUnits,
|
||||
DeliveredChapters: r.DeliveredChapters,
|
||||
TermConsistencyFunded: r.BondFunded,
|
||||
// The user's own click, which no `status` answers: a stop asked for mid-translation can meet
|
||||
|
|
|
|||
|
|
@ -231,13 +231,27 @@ type wireRun struct {
|
|||
Status string `json:"status"`
|
||||
StopForSigning bool `json:"stop_for_signing"`
|
||||
StopRequested bool `json:"stop_requested"`
|
||||
// OrderedChapters and DeliveredChapters are what the run bought and what it has handed over.
|
||||
// `ordered_chapters` is what `ceiling_chapters` was called until 0.11.0, renamed because the word
|
||||
// «ceiling» belonged to money and this number never was money — it is how much BOOK was sold.
|
||||
OrderedChapters int `json:"ordered_chapters"`
|
||||
// DeliveredChapters is null for a run whose order does not close whole chapters — a CHARACTER
|
||||
// order, which buys a prefix of one. Such a run has delivered no chapter, and `0` would read as
|
||||
// «nothing happened»; its progress is the `progress` pair, counted in the unit it was sold in.
|
||||
// OrderedChapters, OrderedUnits and DeliveredChapters are what the run bought and what it has
|
||||
// handed over. `ordered_chapters` is what `ceiling_chapters` was called until 0.11.0, renamed
|
||||
// because the word «ceiling» belonged to money and this number never was money — it is how much
|
||||
// BOOK was sold.
|
||||
//
|
||||
// ⛔ EXACTLY ONE OF THE FIRST TWO IS SET, and which one says what unit this run is measured in —
|
||||
// its bar, its delivery, all of it. A run sold in chapters carries `ordered_chapters`; one sold in
|
||||
// CHARACTERS carries `ordered_units` and a null here, because the chapter figure such a run used
|
||||
// to publish was the SPAN it reached into and overstated what was bought by up to a whole chapter.
|
||||
OrderedChapters *int `json:"ordered_chapters"`
|
||||
// OrderedUnits is how much a run sold in CHARACTERS bought, in the engine's own output units, and
|
||||
// null for a run sold in chapters. ⚠ It is not the number of characters the buyer typed: that
|
||||
// figure is resolved into units when the order is placed and is not stored.
|
||||
OrderedUnits *int `json:"ordered_units"`
|
||||
// DeliveredChapters is null for a run whose order was phrased in CHARACTERS. Such a run is
|
||||
// measured in units end to end, and `0` here would read as «nothing happened» while work is being
|
||||
// done and paid for; its progress is the `progress` pair, counted in the unit it was sold in.
|
||||
//
|
||||
// ⚠ NULL IS THE DISCRIMINATOR THE WIRE ACTUALLY HAS, and it is the only one: `ordered_chapters` is
|
||||
// a positive span for a character order too, so it separates nothing. Null here means «read the
|
||||
// bar as units»; `0` with `ordered_chapters: 0` means a re-pass.
|
||||
DeliveredChapters *int `json:"delivered_chapters"`
|
||||
// TermConsistencyFunded is the order form's promise, kept after the click: whether THIS run's
|
||||
// reservation has room for the book-wide pass that keeps a book's terms consistent. The form
|
||||
|
|
|
|||
|
|
@ -285,7 +285,7 @@ func TestTheBookCardCarriesItsRunOrAnExplicitNull(t *testing.T) {
|
|||
finished := time.Unix(0, 0).UTC()
|
||||
lib.book.Revision = 1900
|
||||
lib.run = &pgstore.Run{ID: "run_1", BookID: "bk_1", Revision: 12, Status: "paused", VerifyBank: true,
|
||||
OrderedChapters: 100, Progress: pgstore.Progress{Done: 40, Total: 100},
|
||||
OrderedChapters: ptr(100), Progress: pgstore.Progress{Done: 40, Total: 100},
|
||||
PausedReason: "credit_exhausted", StartedAt: finished, FinishedAt: &finished}
|
||||
got = decode(t, call(t, v0Server(t, lib, &fakeRuns{}), "GET", "/v0/books/bk_1", ""))
|
||||
run, _ := got["run"].(map[string]any)
|
||||
|
|
@ -446,7 +446,7 @@ func TestAnExhaustedAccountAndAFinishedBookAreToldApartByTheCount(t *testing.T)
|
|||
// a missing field: what `ceiling_chapters` used to guard is now guarded by the hold, which the
|
||||
// server computes from the order it resolved itself.
|
||||
func TestARunTakesTheThreeKindsOfOrderAndDefaultsToTheWholeBook(t *testing.T) {
|
||||
rn := &fakeRuns{run: pgstore.Run{ID: "run_1", Status: "translating", OrderedChapters: 100,
|
||||
rn := &fakeRuns{run: pgstore.Run{ID: "run_1", Status: "translating", OrderedChapters: ptr(100),
|
||||
VerifyBank: true, StartedAt: time.Unix(0, 0).UTC()}}
|
||||
h := v0Server(t, &fakeLibrary{}, rn)
|
||||
|
||||
|
|
@ -690,7 +690,7 @@ func TestAVolumeBelowTheSchemaMinimumIsARejectedRequestAndNotAMovedBound(t *test
|
|||
// a server that refused the whole request would break a client generated against a later 0.x for a
|
||||
// field it was free to ignore.
|
||||
func TestAnUnknownRequestPropertyIsIgnoredRatherThanRefused(t *testing.T) {
|
||||
rn := &fakeRuns{run: pgstore.Run{ID: "run_1", Status: "translating", OrderedChapters: 10}}
|
||||
rn := &fakeRuns{run: pgstore.Run{ID: "run_1", Status: "translating", OrderedChapters: ptr(10)}}
|
||||
w := call(t, v0Server(t, &fakeLibrary{}, rn), "POST", "/v0/books/bk_1/runs",
|
||||
`{"stop_for_signing":true,"chapters":10,"a_field_from_a_later_minor":"x"}`)
|
||||
if w.Code != http.StatusAccepted {
|
||||
|
|
|
|||
|
|
@ -813,7 +813,20 @@ type Run struct {
|
|||
// ⚠ The column behind OrderedChapters is still called `ceiling_chapters`, and the word is the one
|
||||
// thing about it that was wrong: it never was a ceiling in the money sense — the money ceiling is
|
||||
// the hold — it was always how much book the run was sold.
|
||||
OrderedChapters int
|
||||
//
|
||||
// ⛔ NIL FOR A RUN SOLD IN CHARACTERS — see runOrderedChapters for why the row still carries a
|
||||
// number there and only the view is null. OrderedUnits is the figure such a run was sold in.
|
||||
OrderedChapters *int
|
||||
// OrderedUnits is how much this run bought in the engine's own output units, and nil for a run
|
||||
// bought in chapters. It is the other half of OrderedChapters: exactly one of the two is set, and
|
||||
// which one says what unit this run — its bar, its allowance, its delivery — is measured in.
|
||||
//
|
||||
// ⚠ It is NOT the number the buyer typed. A character order is resolved into units at admission
|
||||
// (`runs.resolveOrder` through `pricing.UnitsFor`) and the characters themselves are not stored
|
||||
// anywhere; a reloaded screen therefore answers in units even though the question was asked in
|
||||
// characters. Whether that echo is owed to the buyer is a product question, named in the pack's
|
||||
// report rather than decided here.
|
||||
OrderedUnits *int
|
||||
// DeliveredChapters is NIL for a run whose order does not close whole chapters: such a run has
|
||||
// delivered no CHAPTER, and reporting `0` would read as «nothing happened» rather than as «this
|
||||
// is not the unit this run is measured in». Progress carries what it HAS done instead, and for
|
||||
|
|
@ -842,13 +855,13 @@ type Run struct {
|
|||
// run and `b` its book. Written once because the three that used it wrote the same fourteen columns
|
||||
// three times, in three orders that had to stay in step by hand.
|
||||
const runRow = `r.id, b.id, b.revision, r.status, r.verify_bank, r.stop_requested_at is not null,
|
||||
r.ceiling_chapters, ` + runDelivered + `, r.bond_funded, ` + runDone + `, ` + runTotal + `, ` + runStage + `, r.eta_seconds,
|
||||
` + runOrderedChapters + `, r.ordered_units, ` + runDelivered + `, r.bond_funded, ` + runDone + `, ` + runTotal + `, ` + runStage + `, r.eta_seconds,
|
||||
coalesce(r.paused_reason, ''), coalesce(r.failure_reason, ''), r.started_at, r.finished_at`
|
||||
|
||||
// scanRun reads runRow into a Run, plus whatever the caller selected after it.
|
||||
func scanRun(row pgx.Row, out *Run, extra ...any) error {
|
||||
return row.Scan(append([]any{&out.ID, &out.BookID, &out.Revision, &out.Status, &out.VerifyBank,
|
||||
&out.StopRequested, &out.OrderedChapters, &out.DeliveredChapters, &out.BondFunded,
|
||||
&out.StopRequested, &out.OrderedChapters, &out.OrderedUnits, &out.DeliveredChapters, &out.BondFunded,
|
||||
&out.Progress.Done, &out.Progress.Total,
|
||||
&out.Progress.Stage, &out.Progress.ETASeconds, &out.PausedReason, &out.FailureReason,
|
||||
&out.StartedAt, &out.FinishedAt}, extra...)...)
|
||||
|
|
|
|||
|
|
@ -119,7 +119,10 @@ func TestTheBookCardCarriesARevisionEitherWay(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if run == nil || run.ID != started.ID || run.OrderedChapters != 10 || run.VerifyBank {
|
||||
// ⚠ A CHAPTER-SHAPED run, so the figure is present; nil here would mean «this run is not measured
|
||||
// in chapters» (runOrderedChapters), which is a different answer from a wrong number.
|
||||
if run == nil || run.ID != started.ID || run.OrderedChapters == nil || *run.OrderedChapters != 10 ||
|
||||
run.VerifyBank {
|
||||
t.Fatalf("card run: %+v", run)
|
||||
}
|
||||
if run.PausedReason != "" || run.FinishedAt != nil {
|
||||
|
|
|
|||
|
|
@ -688,6 +688,25 @@ const (
|
|||
then 'editing' else 'drafting' end)`
|
||||
)
|
||||
|
||||
// runOrderedChapters is what the contract calls `ordered_chapters`: how much BOOK this run was sold,
|
||||
// in chapters — and NULL for a run that was not sold in chapters at all.
|
||||
//
|
||||
// ⛔ THE NULL IS THE POINT, and the figure it replaces was a lie of a particular kind: not a wrong
|
||||
// number, but a right number answering a question nobody asked. The column underneath
|
||||
// (`ceiling_chapters`) holds, for an order phrased in CHARACTERS, the chapter SPAN — how many
|
||||
// chapters the order reaches INTO — and that overstates what was bought by as much as a whole
|
||||
// chapter: an order of one unit out of four in the first chapter stored `1`, which is the cheapest
|
||||
// order there is and the one a person tries the service with. Measured, not reasoned: see
|
||||
// `runs.TestACharacterOrdersVolumeIsPublishedInTheUnitItWasSoldIn`.
|
||||
//
|
||||
// ⚠ IT NULLS THE VIEW AND NOT THE COLUMN, deliberately. `ceiling_chapters = 0` is the RE-PASS's own
|
||||
// mark, and three places read it as exactly that — `runs.maxUnitsFor`, the reconciler's resume gate,
|
||||
// and this package's own admission check. Writing a zero or a null into the row would make a
|
||||
// character order indistinguishable from a re-pass to all three, and in the first of them the cost is
|
||||
// money: zero is the engine's word for «no volume bound at all». So the row keeps the span, every
|
||||
// internal reader keeps its meaning, and only what goes OUT changes.
|
||||
const runOrderedChapters = `(case when r.ordered_units is not null then null else r.ceiling_chapters end)`
|
||||
|
||||
// newestRun is the order in which a book RESOLVES to one of its runs: the LIVE one first, however the
|
||||
// clocks fell, and only then the newest-started.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -45,10 +45,15 @@ type StartRunInput struct {
|
|||
// a blanket yes (D20.2-Q2).
|
||||
Resnapshot bool
|
||||
AcceptRebill money.MicroUSD
|
||||
// OrderedUnits is set ONLY for a run whose order does not close whole chapters — a CHARACTER
|
||||
// order. Nil is the ordinary shape and leaves the run's bar counted in chapters, exactly as
|
||||
// before this pack. See migration 00033 for why such a run needs a bar of its own: counted in
|
||||
// chapters it reads `0/N` for its whole life, which is the state the admission refuses a book for.
|
||||
// OrderedUnits is set for every run whose order was phrased in CHARACTERS, and nil for every other
|
||||
// shape. Nil is the ordinary case and leaves the run's bar counted in chapters, exactly as before
|
||||
// this pack. See migration 00033 for why such a run needs a bar of its own: counted in chapters an
|
||||
// order that stops inside a chapter reads `0/N` for its whole life, which is the state the
|
||||
// admission refuses a book for.
|
||||
//
|
||||
// ⚠ THE CONDITION IS THE PHRASING, NOT WHERE THE ORDER LANDED. An earlier edition of this line
|
||||
// said «only for a run whose order does not close whole chapters», and that is measurably false —
|
||||
// see pricing.Quote.UnitShaped for the measurement and for why the phrasing is the right test.
|
||||
OrderedUnits *int
|
||||
// BondFunded is whether this run's hold has room for the book-level consistency passes on top of
|
||||
// the work it bought. Recorded on the RUN because the order form's answer is a QUOTE and the
|
||||
|
|
@ -153,12 +158,13 @@ func (s *Store) StartRun(ctx context.Context, in StartRunInput, journalOffset in
|
|||
where c.book_id = b.id and c.units_total > 0 and c.units_draft_done >= c.units_total),
|
||||
` + bookUnitsEditDone + `, ` + bookUnitsDraftDone + `
|
||||
from books b where b.id = $2 and b.owner_id = $6
|
||||
returning id, book_id, revision, status, verify_bank, ceiling_chapters,
|
||||
coalesce(paused_reason, ''), started_at, finished_at`
|
||||
returning id, book_id, revision, status, verify_bank,
|
||||
(case when ordered_units is not null then null else ceiling_chapters end),
|
||||
ordered_units, coalesce(paused_reason, ''), started_at, finished_at`
|
||||
err := tx.QueryRow(ctx, insertRun, runID, in.BookID, in.VerifyBank, in.OrderedChapters, in.Now, in.UserID,
|
||||
in.Resnapshot, int64(in.AcceptRebill), in.BondFunded, in.OrderedUnits).
|
||||
Scan(&out.ID, &out.BookID, &out.Revision, &out.Status, &out.VerifyBank, &out.OrderedChapters,
|
||||
&out.PausedReason, &out.StartedAt, &out.FinishedAt)
|
||||
&out.OrderedUnits, &out.PausedReason, &out.StartedAt, &out.FinishedAt)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return ErrNoBook // the book is missing, or it is not this account's
|
||||
}
|
||||
|
|
|
|||
|
|
@ -140,9 +140,20 @@ type Quote struct {
|
|||
ThroughChapter int
|
||||
// Units is how many output units the order buys — the figure `--max-units` is derived from.
|
||||
Units int
|
||||
// UnitShaped says the order does not close whole chapters: it stops inside one. Only a CHARACTER
|
||||
// order can, and it is the reason such a run needs a bar counted in units — in chapters it would
|
||||
// read `0/N` for its whole life, because a chapter counts only once every unit in it is done.
|
||||
// UnitShaped says the order was phrased in CHARACTERS, and therefore is measured in units all the
|
||||
// way through: the bar counts units, and `delivered_chapters` is null.
|
||||
//
|
||||
// ⚠ IT IS ABOUT THE PHRASING, NOT ABOUT WHERE THE ORDER LANDED — an earlier edition of this line
|
||||
// said «does not close whole chapters», and that is measurably false: `UnitsFor` returns the first
|
||||
// unit whose running sum covers what was asked, so an order lands on a chapter boundary roughly as
|
||||
// often as chapters divide units (a third of them at three units a chapter), and such an order
|
||||
// closes whole chapters while staying unit-shaped. Pinned by
|
||||
// `runs.TestACharacterOrdersVolumeIsPublishedInTheUnitItWasSoldIn`.
|
||||
//
|
||||
// The reason to measure in the unit the buyer SPOKE, rather than in whatever the order happens to
|
||||
// cover, is that two purchases a person cannot tell apart must not render differently. The reason
|
||||
// it cannot be chapters is older: a chapter counts only once every unit in it is done, so an order
|
||||
// that stops inside one would read `0/N` for its whole life.
|
||||
UnitShaped bool
|
||||
// Expected is what the engine expects the ordered work to be BILLED. It is the honest half of the
|
||||
// pair a buyer is shown («ожидаемо ≈ …, зарезервируем до …»).
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"time"
|
||||
|
||||
"textmachine/platform/internal/ingest"
|
||||
"textmachine/platform/internal/money"
|
||||
"textmachine/platform/internal/pgstore"
|
||||
)
|
||||
|
||||
|
|
@ -644,3 +645,77 @@ func TestTheMaterializationBackoffGrowsAndIsCapped(t *testing.T) {
|
|||
t.Errorf("the retry of a long-dead book is %v, want the cap", got)
|
||||
}
|
||||
}
|
||||
|
||||
// ⛔ THE SEAM BETWEEN THE ENGINE'S DOCUMENT AND THE STORE'S ROW, and it went untested until it broke.
|
||||
//
|
||||
// Both halves had tests: `ingest` decides whether a manifest is priced, `pgstore` writes what it is
|
||||
// handed. What nobody asked was whether this function hands over the RIGHT THING — and the answer was
|
||||
// no. `pgstore.Structure` carried the book's character count in two places at once (its own field and
|
||||
// the wire struct's), this mapping filled one of them, and the row went in null. The type no longer
|
||||
// admits that, so what is pinned here is the mapping's SHAPE: every money figure arrives, and the
|
||||
// character count arrives on the CUT, where it survives a price this build cannot read.
|
||||
//
|
||||
// Mutation caught: dropping a member of the Projection literal; reading SourceChars off the price
|
||||
// object again; gating the character count on `Priced`.
|
||||
func TestTheProjectionReachesTheStoreWholeAndTheCharacterCountTravelsBesideIt(t *testing.T) {
|
||||
m := tree()
|
||||
m.Structure = ingest.StructureDetected
|
||||
m.Price = &ingest.BookPrice{ExpectedUSD: 2_090_000, BookOnceUSD: 2_000_000, StepMaxUSD: 69_828, SourceChars: 3000}
|
||||
pricedTree(&m)
|
||||
store := &fakeStore{}
|
||||
svc := &Service{Store: store, Binary: "tmctl", Engine: &fakeEngine{manifest: m, noEnvelope: true}}
|
||||
_ = svc.Refresh(t.Context(), owedBook(t.TempDir())) // the bank half fails; the tree lands anyway
|
||||
if !store.saved {
|
||||
t.Fatal("the tree did not land at all")
|
||||
}
|
||||
got := store.structure
|
||||
if got.Price == nil {
|
||||
t.Fatal("a manifest this build read whole reached the store with no projection")
|
||||
}
|
||||
if got.Price.Expected != 2_090_000 || got.Price.BookOnce != 2_000_000 || got.Price.StepMax != 69_828 {
|
||||
t.Errorf("a money figure was lost in the mapping: %+v", *got.Price)
|
||||
}
|
||||
// ⛔ ON THE CUT, and this is the assertion the defect would have failed: the count is a property of
|
||||
// the TEXT and the store keeps it apart from the money.
|
||||
if got.SourceChars != 3000 {
|
||||
t.Errorf("the engine's rune count reached the store as %d", got.SourceChars)
|
||||
}
|
||||
if got.Structure != ingest.StructureDetected {
|
||||
t.Errorf("the cut's provenance reached the store as %q", got.Structure)
|
||||
}
|
||||
}
|
||||
|
||||
// …and the other direction of the same independence: a manifest whose MONEY this build cannot read
|
||||
// still delivers the count of its text. The refusal is `ingest`'s (a chapter with no price), so what
|
||||
// is pinned here is that this mapping does not widen it into the character count.
|
||||
func TestAPriceThisBuildCannotReadDoesNotCostTheStoreItsCharacterCount(t *testing.T) {
|
||||
m := tree()
|
||||
m.Structure = ingest.StructureDetected
|
||||
m.Price = &ingest.BookPrice{ExpectedUSD: 2_090_000, BookOnceUSD: 2_000_000, StepMaxUSD: 69_828, SourceChars: 3000}
|
||||
pricedTree(&m)
|
||||
// One unit loses its bill, and `ingest` then refuses the projection as a whole — the half-read
|
||||
// shape is the dangerous one. The book's own `source_chars` is untouched and must still travel.
|
||||
m.Chapters[0].Units[0].Price = nil
|
||||
store := &fakeStore{}
|
||||
svc := &Service{Store: store, Binary: "tmctl", Engine: &fakeEngine{manifest: m, noEnvelope: true}}
|
||||
_ = svc.Refresh(t.Context(), owedBook(t.TempDir()))
|
||||
if store.structure.Price != nil {
|
||||
t.Errorf("a projection this build could not read whole was written anyway: %+v", *store.structure.Price)
|
||||
}
|
||||
if store.structure.SourceChars != 3000 {
|
||||
t.Errorf("the character count went down with the price: %d", store.structure.SourceChars)
|
||||
}
|
||||
}
|
||||
|
||||
// pricedTree gives `tree()` a projection its own witness accepts: 30_000 micro-USD and 1000 runes a
|
||||
// unit, the chapters' roll-up summing to the book's figure less the flat book-level bond.
|
||||
func pricedTree(m *ingest.Manifest) {
|
||||
for i := range m.Chapters {
|
||||
total := money.MicroUSD(0)
|
||||
for j := range m.Chapters[i].Units {
|
||||
m.Chapters[i].Units[j].Price = &ingest.UnitPrice{ExpectedUSD: 30_000, SourceChars: 1000}
|
||||
total += 30_000
|
||||
}
|
||||
m.Chapters[i].Price = &ingest.UnitPrice{ExpectedUSD: total, SourceChars: int64(len(m.Chapters[i].Units)) * 1000}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -627,8 +627,8 @@ func TestTheVolumeAllowanceCountsUnitsAndNotChapters(t *testing.T) {
|
|||
}
|
||||
// …and what was SOLD is still counted in chapters, because that is what the buyer chose and what
|
||||
// the run's own bar is measured in.
|
||||
if got := f.run(t, run.ID).OrderedChapters; got != 2 {
|
||||
t.Errorf("the run says it bought %d chapters", got)
|
||||
if got := f.run(t, run.ID).OrderedChapters; got == nil || *got != 2 {
|
||||
t.Errorf("the run says it bought %v chapters", got)
|
||||
}
|
||||
// The hold is the units' money, not the chapters': eight units at the fixture's rate.
|
||||
if got, want := f.account(t).Reserved, fixtureHold(8); got != want {
|
||||
|
|
@ -712,6 +712,16 @@ func TestAChapterOrderKeepsTheBarItAlwaysHad(t *testing.T) {
|
|||
if got.DeliveredChapters == nil || *got.DeliveredChapters != 0 {
|
||||
t.Errorf("a chapter order reports delivered chapters as %v, want 0 rather than null", got.DeliveredChapters)
|
||||
}
|
||||
// …and the pair that says which unit this run is measured in points the other way from the
|
||||
// character order's: the chapter figure is present, the unit figure is not. Exactly one is set,
|
||||
// and this is the half that would go silently null if the branch asked «is this a partial order»
|
||||
// instead of «was this sold in characters».
|
||||
if got.OrderedChapters == nil || *got.OrderedChapters != 2 {
|
||||
t.Errorf("a two-chapter order publishes ordered_chapters %v, want 2", got.OrderedChapters)
|
||||
}
|
||||
if got.OrderedUnits != nil {
|
||||
t.Errorf("a run sold in chapters publishes ordered_units %d; the two are exclusive", *got.OrderedUnits)
|
||||
}
|
||||
// One whole chapter delivered moves both the bar and the delivered count.
|
||||
if _, err := f.store.Pool().Exec(f.ctx, `
|
||||
update chapters set units_draft_done = 2, units_edit_done = 2
|
||||
|
|
@ -726,3 +736,54 @@ func TestAChapterOrderKeepsTheBarItAlwaysHad(t *testing.T) {
|
|||
t.Errorf("one whole chapter through both passes reads %d/%d", got.Progress.Done, got.Progress.Total)
|
||||
}
|
||||
}
|
||||
|
||||
// ⛔ A CHARACTER ORDER PUBLISHES ITS VOLUME IN THE UNIT IT WAS SOLD IN, and says NOTHING in chapters.
|
||||
//
|
||||
// ⚠ THIS TEST REPLACES ONE THAT PINNED THE OPPOSITE, and the replacement is ordered rather than
|
||||
// convenient (D39.183 — declared in the pack's report). Its predecessor,
|
||||
// `TestACharacterOrderReportsTheChapterSpanItReachesIntoAndNotZero`, pinned the SPAN — how many
|
||||
// chapters the order reaches into — because that was what the build published and the contract
|
||||
// described a zero. Measuring it is what killed it: the span OVERSTATES what was bought by as much as
|
||||
// a whole chapter, and worst on the cheapest order there is. One unit out of four in the first
|
||||
// chapter published «1 chapter», which a person reads as «my money reached the end of chapter one».
|
||||
// The guarantee did not disappear — it moved: what the buyer got is now published as `ordered_units`,
|
||||
// in the unit they asked in, and `ordered_chapters` is null, which is the honest answer to a question
|
||||
// that does not apply.
|
||||
//
|
||||
// ⚠ The row underneath still holds the span — see pgstore.runOrderedChapters — because
|
||||
// `ceiling_chapters = 0` is the RE-PASS's mark and three internal readers depend on it.
|
||||
//
|
||||
// Mutation caught: publishing the span again; nulling BOTH figures; nulling the chapter figure for a
|
||||
// CHAPTER order (the branch is `ordered_units is not null`, not «is this a partial order»).
|
||||
func TestACharacterOrdersVolumeIsPublishedInTheUnitItWasSoldIn(t *testing.T) {
|
||||
f := newFixture(t, "10", 500)
|
||||
book := multiUnitBook(t, f, "span", 3, 4) // three chapters, four units each, 1000 runes a unit
|
||||
// 4500 runes ⇒ five units ⇒ the whole of chapter one and one unit of chapter two.
|
||||
chars := int64(4500)
|
||||
run, err := f.svc.Start(f.ctx, StartRequest{UserID: "u1", BookID: book, Characters: &chars})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := f.run(t, run.ID)
|
||||
if got.OrderedChapters != nil {
|
||||
t.Errorf("a run sold in characters reports ordered_chapters %d: the chapter figure it used to "+
|
||||
"publish was the SPAN it reached into, and it overstated what was bought", *got.OrderedChapters)
|
||||
}
|
||||
if got.OrderedUnits == nil || *got.OrderedUnits != 5 {
|
||||
t.Errorf("a five-unit order publishes ordered_units %v, want 5 — the unit it was sold in", got.OrderedUnits)
|
||||
}
|
||||
// …and the two figures that DO speak the order's own unit say so, which is what makes the span
|
||||
// readable rather than misleading: the bar counts units and no chapter is claimed as delivered.
|
||||
if got.Progress.Total != 10 {
|
||||
t.Errorf("the bar of a five-unit order over two waves reads /%d, want /10", got.Progress.Total)
|
||||
}
|
||||
if got.DeliveredChapters != nil {
|
||||
t.Errorf("delivered_chapters is %d rather than null on a unit-shaped run", *got.DeliveredChapters)
|
||||
}
|
||||
// ⚠ AND THE ORDER CLOSED A WHOLE CHAPTER — chapter one, all four of its units — while still being
|
||||
// unit-shaped. So «unit-shaped» means «phrased in characters», NOT «closes no whole chapter»,
|
||||
// which is how three doc comments and the canon describe it.
|
||||
if got.Progress.Done != 0 {
|
||||
t.Errorf("nothing has been delivered yet and the bar reads %d", got.Progress.Done)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -474,8 +474,14 @@ func (s *Service) Start(ctx context.Context, in StartRequest) (pgstore.Run, erro
|
|||
UserID: in.UserID,
|
||||
BookID: in.BookID,
|
||||
VerifyBank: in.VerifyBank,
|
||||
// The RUN's own bar is still counted in chapters (canon §Progress), so it carries how many the
|
||||
// order spans; the ORDER itself lives on the book, in units.
|
||||
// How many chapters the order SPANS. For an order phrased in chapters that is what was bought,
|
||||
// and it is what the bar counts and what the wire publishes. For one phrased in CHARACTERS it
|
||||
// is only stored: the span overstates what was bought — a single unit of a four-unit chapter
|
||||
// spans `1` — so the wire publishes `ordered_units` instead and nulls this
|
||||
// (pgstore.runOrderedChapters). ⚠ It is never 0 for either shape (`max(chapters, 1)` in
|
||||
// QuoteUnits), and that is load-bearing: 0 is the RE-PASS's mark. Pinned by
|
||||
// runs.TestACharacterOrdersVolumeIsPublishedInTheUnitItWasSoldIn and its chapter-shaped twin.
|
||||
// The ORDER itself lives on the book, in units.
|
||||
OrderedChapters: quote.Chapters,
|
||||
Ceiling: quote.Hold,
|
||||
Now: s.now(),
|
||||
|
|
@ -486,9 +492,10 @@ func (s *Service) Start(ctx context.Context, in StartRequest) (pgstore.Run, erro
|
|||
// that is not — and the passes degrade rather than halt, which makes the difference invisible
|
||||
// in the book. The run carries what it really got.
|
||||
BondFunded: quote.BondFunded,
|
||||
// ⚠ SET ONLY FOR AN ORDER THAT DOES NOT CLOSE WHOLE CHAPTERS. It is what switches the run's bar
|
||||
// to units — see migration 00033: counted in chapters, such a run reads `0/N` for its whole
|
||||
// life, which is the state this very file refuses a book for at admission (PD-405).
|
||||
// ⚠ SET FOR EVERY ORDER PHRASED IN CHARACTERS — including one that happens to land on a chapter
|
||||
// boundary, see pricing.Quote.UnitShaped. It is what switches the run's bar to units: counted
|
||||
// in chapters, an order that stops inside one reads `0/N` for its whole life, which is the
|
||||
// state this very file refuses a book for at admission (PD-405). See migration 00033.
|
||||
OrderedUnits: unitShapedOrder(quote),
|
||||
Order: &order,
|
||||
}, offset, s.enqueue)
|
||||
|
|
@ -562,9 +569,12 @@ func (s *Service) resolveOrder(ctx context.Context, in StartRequest, priced pgst
|
|||
// ⛔ PRICED FROM THE UNITS THEMSELVES, not from the chapters they fall in. Quoting a unit
|
||||
// order at the price of every chapter it touches is what made this order meaningless exactly
|
||||
// where it is the only one available: a book with no chapter structure is ONE chapter, so a
|
||||
// thousand characters of it reserved the whole book. The chapter span below is carried only
|
||||
// for the RUN's bar, which is still counted in chapters (canon §Progress), and it is the
|
||||
// coarser of the two figures — a character order can stop inside a chapter.
|
||||
// thousand characters of it reserved the whole book. The chapter span below goes into the ROW
|
||||
// and no further: nothing publishes it any more (pgstore.runOrderedChapters nulls the view for
|
||||
// a unit-shaped run, because the span overstates what was bought), and the bar counts units
|
||||
// because `UnitShaped` is set two lines down. What the stored span is still FOR is the re-pass
|
||||
// discriminator — `ceiling_chapters = 0` is the re-pass's own mark, and a zero written here
|
||||
// would make this order look like one to three internal readers.
|
||||
q := s.Pricing.QuoteUnits(pb, balance, units, n, chaptersSpanning(pb, n))
|
||||
q.UnitShaped = true
|
||||
return q, pgstore.BookOrder{ThroughUnitID: units[n-1].ID}, nil
|
||||
|
|
@ -629,8 +639,11 @@ func (s *Service) ceilingFor(amount money.MicroUSD) ([]string, error) {
|
|||
return s.Cfg.Ceiling.Args(amount)
|
||||
}
|
||||
|
||||
// unitShapedOrder is the run's own volume when its order does not close whole chapters, and nil when
|
||||
// it does. Nil is the ordinary shape and leaves the bar counted in chapters exactly as before.
|
||||
// unitShapedOrder is the run's own volume when its order was phrased in CHARACTERS, and nil for every
|
||||
// other shape. Nil is the ordinary case and leaves the bar counted in chapters exactly as before.
|
||||
//
|
||||
// ⚠ The condition is the PHRASING (`q.UnitShaped`), not where the order landed — see
|
||||
// pricing.Quote.UnitShaped for why, and for the measurement that killed the older wording.
|
||||
func unitShapedOrder(q pricing.Quote) *int {
|
||||
if !q.UnitShaped || q.Units <= 0 {
|
||||
return nil
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue