Land the platform zone's re-aimed anchors in its defect register and journal, where its own pack had moved the targets

This commit is contained in:
heaven 2026-09-17 21:17:07 +03:00
parent ba734199d4
commit 3c6920ad8f
2 changed files with 26 additions and 26 deletions

File diff suppressed because one or more lines are too long

View file

@ -289,7 +289,7 @@ go version → go1.26.7 ≥ 1.26.6
| Находка | Чья ошибка | Что сделано | Чем предъявлено | | Находка | Чья ошибка | Что сделано | Чем предъявлено |
|---|---|---|---| |---|---|---|---|
| **F1.** Канон описывал происхождение согласия У́ЖЕ, чем оно есть: «даёт `resume` над поправленным банком» — а `runs.go:470`=`resnapshot := book.BankMoved \|\| book.HasPriorRun` плюс `:579`=`func rebillConsent` дают non-null согласие и на ОБЫЧНОЙ второй покупке книги | моя, в тексте канона | описание расширено до истинной популяции: согласие несёт всякий прогон, идущий с пере-снапшотом, и назван довод, почему вторая покупка — защита, а не аппетит (проекция, выросшая за виденное покупателем, обязана ОТКАЗАТЬ, а не быть купленной молча). Фраза «never by the platform's own restart» оставлена: свип передаёт нули | пере-снято мной по коду до правки; канон разобран YAML-парсером | | **F1.** Канон описывал происхождение согласия У́ЖЕ, чем оно есть: «даёт `resume` над поправленным банком» — а `platform/internal/runs/runs.go:470`=`resnapshot := book.BankMoved` плюс `:579`=`func rebillConsent` дают non-null согласие и на ОБЫЧНОЙ второй покупке книги | моя, в тексте канона | описание расширено до истинной популяции: согласие несёт всякий прогон, идущий с пере-снапшотом, и назван довод, почему вторая покупка — защита, а не аппетит (проекция, выросшая за виденное покупателем, обязана ОТКАЗАТЬ, а не быть купленной молча). Фраза «never by the platform's own restart» оставлена: свип передаёт нули | пере-снято мной по коду до правки; канон разобран YAML-парсером |
| **F2.** `progress_lagging` объявлен «Always sent», а в `Run.required` его не было ⇒ клиент, сгенерированный по канону, сделал бы член опциональным — канон противоречил бы себе на одном свойстве | моя | член внесён в `required` (12 вместо 11), рядом — довод и ссылка на зеркальный корректирующий минор `0.13.1` | `requiredOf` парсит канон: `progress_lagging` в required = да, `rebill_consent_micro_usd` = нет (nullable по замыслу) | | **F2.** `progress_lagging` объявлен «Always sent», а в `Run.required` его не было ⇒ клиент, сгенерированный по канону, сделал бы член опциональным — канон противоречил бы себе на одном свойстве | моя | член внесён в `required` (12 вместо 11), рядом — довод и ссылка на зеркальный корректирующий минор `0.13.1` | `requiredOf` парсит канон: `progress_lagging` в required = да, `rebill_consent_micro_usd` = нет (nullable по замыслу) |
| **F2⭐ класс, а не случай** | предложение оркестратора, аргумент мой | построен гейт `httpapi.TestEveryAlwaysSentMemberOfARunIsPromisedByTheCanon`: он ходит по ЖСОНу, который провод отдаёт для пустого прогона, и требует, чтобы каждый всегда-посылаемый член был либо в `required`, либо в ИМЕННОМ реестре исключений с причиной. Реестр честен в обе стороны: исключённый член, который канон уже обещает, и исключённый член, которого провод не шлёт, — обе строки краснеют | две посадки каталога: канон перестаёт обещать член · провод переименовывает член | | **F2⭐ класс, а не случай** | предложение оркестратора, аргумент мой | построен гейт `httpapi.TestEveryAlwaysSentMemberOfARunIsPromisedByTheCanon`: он ходит по ЖСОНу, который провод отдаёт для пустого прогона, и требует, чтобы каждый всегда-посылаемый член был либо в `required`, либо в ИМЕННОМ реестре исключений с причиной. Реестр честен в обе стороны: исключённый член, который канон уже обещает, и исключённый член, которого провод не шлёт, — обе строки краснеют | две посадки каталога: канон перестаёт обещать член · провод переименовывает член |
| **F4.** `failureReasonsInDDL` читает миграцию целиком, не различая `+goose Up` и `+goose Down` | моя | условие, при котором вывод перестаёт держаться, дописано в хелпере, и названо лечение (резать файл по маркеру `+goose Down`, а не нацеливать гейт на файл по имени) | по нашему же правилу про выводы в комментариях | | **F4.** `failureReasonsInDDL` читает миграцию целиком, не различая `+goose Up` и `+goose Down` | моя | условие, при котором вывод перестаёт держаться, дописано в хелпере, и названо лечение (резать файл по маркеру `+goose Down`, а не нацеливать гейт на файл по имени) | по нашему же правилу про выводы в комментариях |
@ -1563,7 +1563,7 @@ re-pass не был запинен) и M26 (ветвь проигравшего)
дерево глав · живой прогон), и форма заказа о них не знала: вердикт считался из остатка книги и баланса, дерево глав · живой прогон), и форма заказа о них не знала: вердикт считался из остатка книги и баланса,
а `ErrRunInFlight` жил только у двери. Человек видел `covers_all`, жал и получал 409. а `ErrRunInFlight` жил только у двери. Человек видел `covers_all`, жал и получал 409.
Сделано: три проверки вынесены в **один предикат** `platform/internal/runs/runs.go:666`=`func startable`, Сделано: три проверки вынесены в **один предикат** `platform/internal/runs/runs.go:689`=`func startable`,
у него две привязки РАЗНОЙ авторитетности — `Start` зовёт его под замком книги (решает), `Order` зовёт у него две привязки РАЗНОЙ авторитетности — `Start` зовёт его под замком книги (решает), `Order` зовёт
на опрашиваемом пути (`runs.go:304`, совещательно и заведомо устаревает). Мерило пака — «сколько на опрашиваемом пути (`runs.go:304`, совещательно и заведомо устаревает). Мерило пака — «сколько
ОПРЕДЕЛЕНИЙ придётся тронуть, если правило изменится» — выполнено: одно. ОПРЕДЕЛЕНИЙ придётся тронуть, если правило изменится» — выполнено: одно.
@ -1593,7 +1593,7 @@ re-pass не был запинен) и M26 (ветвь проигравшего)
### `PD-162` — отказ до денег, и вторая дверь, которой ряд не называл ### `PD-162` — отказ до денег, и вторая дверь, которой ряд не называл
Проверка каталога книги стоит на допуске ДО холда (`runs.go:707`=`func (s *Service) sourceThere`, зовётся Проверка каталога книги стоит на допуске ДО холда (`platform/internal/runs/runs.go:741`=`func (s *Service) sourceThere`, зовётся
из `Start` после предиката книги и перед чтением счёта). ⚠ **Трудности, которую промт объявлял главной, из `Start` после предиката книги и перед чтением счёта). ⚠ **Трудности, которую промт объявлял главной,
действительно нет** (§4.3 промта): каталог создаётся на интейке ДО строки в БД, а исключение первого действительно нет** (§4.3 промта): каталог создаётся на интейке ДО строки в БД, а исключение первого
прогона принадлежит ЖУРНАЛУ внутри каталога (`journalSize` мапит ENOENT в нулевой офсет) — так что прогона принадлежит ЖУРНАЛУ внутри каталога (`journalSize` мапит ENOENT в нулевой офсет) — так что
@ -1619,7 +1619,7 @@ re-pass не был запинен) и M26 (ветвь проигравшего)
Предикат корня не скопирован, а **вызван**: `books.storageIsThere` стал экспортируемым Предикат корня не скопирован, а **вызван**: `books.storageIsThere` стал экспортируемым
`platform/internal/books/books.go:606`=`func StorageIsThere(booksDir string) bool`, а «книга под нашим `platform/internal/books/books.go:606`=`func StorageIsThere(booksDir string) bool`, а «книга под нашим
корнем?» — `books.go:594`=`func Owns(booksDir, dir string) bool`; методы остались обёртками в одну корнем?» — `platform/internal/books/books.go:626`=`func Owns(booksDir, dir string) bool`; методы остались обёртками в одну
строку. Копия была бы вторым экземпляром правила, у которого первая редакция стоила зоны всех книг строку. Копия была бы вторым экземпляром правила, у которого первая редакция стоила зоны всех книг
хоста — и мутация, ломающая `StorageIsThere`, красит не только мой тест, но и ДВА чужих пина `PD-192` хоста — и мутация, ломающая `StorageIsThere`, красит не только мой тест, но и ДВА чужих пина `PD-192`
в `internal/books`, что и есть доказательство, что предикат один. в `internal/books`, что и есть доказательство, что предикат один.
@ -1634,14 +1634,14 @@ re-pass не был запинен) и M26 (ветвь проигравшего)
⚠ Ниже — ТОЛЬКО разбор; кода по этому ряду я не написал ни строки. ⚠ Ниже — ТОЛЬКО разбор; кода по этому ряду я не написал ни строки.
**Механика, снятая чтением (адреса пере-сняты сегодня).** `Resume` читает состояние прогона ДО замка **Механика, снятая чтением (адреса пере-сняты сегодня).** `Resume` читает состояние прогона ДО замка
книги: `platform/internal/runs/reconcile.go:1702`=`s.Store.ReadRunForResume`, затем книги: `platform/internal/runs/reconcile.go:1804`=`s.Store.ReadRunForResume`, затем
`reconcile.go:1663`=`unlock, err := s.lockBook(ctx, l.BookID)`, и только потом судит по `l.Status` `platform/internal/runs/reconcile.go:1815`=`unlock, err := s.lockBook(ctx, l.BookID)`, и только потом судит по `l.Status`
(`reconcile.go:1696`) — по значению, прочитанному ДО замка. Из этого следуют два разных проигравших: (`reconcile.go:1696`) — по значению, прочитанному ДО замка. Из этого следуют два разных проигравших:
* **проигравший ВНУТРИ окна** (его чтение случилось до коммита победителя) держит устаревший * **проигравший ВНУТРИ окна** (его чтение случилось до коммита победителя) держит устаревший
`stopped`, доходит до `reopen`, его вставка `run_attempts` падает на уникальном индексе `stopped`, доходит до `reopen`, его вставка `run_attempts` падает на уникальном индексе
`run_attempts_run_id_attempt_no_key``ErrNoRun``Resume` отвечает **прогоном** `run_attempts_run_id_attempt_no_key``ErrNoRun``Resume` отвечает **прогоном**
(`reconcile.go:1736`=`return s.Store.ReadRun(ctx, userID, runID)`), и холд не берётся: транзакция (`platform/internal/runs/reconcile.go:1973`=`return s.Store.ReadRun(ctx, userID, runID)`), и холд не берётся: транзакция
откатывается целиком; откатывается целиком;
* **проигравший СНАРУЖИ окна** (его чтение случилось уже после коммита победителя) видит * **проигравший СНАРУЖИ окна** (его чтение случилось уже после коммита победителя) видит
`translating`, падает в `default:` и получает `ErrNotResumable` — «the run cannot be continued: it `translating`, падает в `default:` и получает `ErrNotResumable` — «the run cannot be continued: it
@ -1649,7 +1649,7 @@ re-pass не был запинен) и M26 (ветвь проигравшего)
**Окно дефекта — НЕ гонка за индекс, а интервал между чтением состояния и взятием замка.** И отсюда **Окно дефекта — НЕ гонка за индекс, а интервал между чтением состояния и взятием замка.** И отсюда
же объяснение, почему изолированно тест зелёный, а в полном пакете под нагрузкой красный: замок книги же объяснение, почему изолированно тест зелёный, а в полном пакете под нагрузкой красный: замок книги
(`platform/internal/runs/bank.go:400`=`func (s *Service) lockBook`) — ВНУТРИПРОЦЕССНЫЙ мьютекс, так что (`platform/internal/runs/bank.go:439`=`func (s *Service) lockBook`) — ВНУТРИПРОЦЕССНЫЙ мьютекс, так что
два резюма одного демона идут друг за другом, и под голоданием по процессору горутины стартуют дальше два резюма одного демона идут друг за другом, и под голоданием по процессору горутины стартуют дальше
друг от друга — проигравший чаще успевает прочитать уже переведённое состояние. Формулировка в шапке друг от друга — проигравший чаще успевает прочитать уже переведённое состояние. Формулировка в шапке
теста («both calls pass the state check together and race for attempt N+1») описывает состояние ДВУХ теста («both calls pass the state check together and race for attempt N+1») описывает состояние ДВУХ
@ -2901,7 +2901,7 @@ ratified canon is 0.13.0». Улика: файл-носитель (`internal/htt
| `docs/PROGRESS.md`, `docs/architecture/05-decisions-log.md` | **4** | ЧУЖАЯ зона — ушли пингом с готовыми адресами и токенами | | `docs/PROGRESS.md`, `docs/architecture/05-decisions-log.md` | **4** | ЧУЖАЯ зона — ушли пингом с готовыми адресами и токенами |
**Находка из этого же хода:** экранирование `\|` в ячейке регистра (§4.5) **ломает якорь**, если черта **Находка из этого же хода:** экранирование `\|` в ячейке регистра (§4.5) **ломает якорь**, если черта
попала в его токен. `PD-422` держал `internal/runs/runs.go:447`=`resnapshot := book.BankMoved || book.HasPriorRun`; попала в его токен. `PD-422` держал `internal/runs/runs.go:470`=`resnapshot := book.BankMoved || book.HasPriorRun`;
после экранирования токен перестал совпадать с кодом. Вылечено укорочением токена до после экранирования токен перестал совпадать с кодом. Вылечено укорочением токена до
`resnapshot := book.BankMoved` (единственный хит в файле). Счёт колонок и сверка токена тянут ячейку в `resnapshot := book.BankMoved` (единственный хит в файле). Счёт колонок и сверка токена тянут ячейку в
разные стороны — следующий, кто пойдёт экранировать черты, наступит на то же. Ушло пингом. разные стороны — следующий, кто пойдёт экранировать черты, наступит на то же. Ушло пингом.