diff --git a/docs/PLATFORM_P12_SESSION_PROMPT.md b/docs/PLATFORM_P12_SESSION_PROMPT.md index 0e82f3f2..85213270 100644 --- a/docs/PLATFORM_P12_SESSION_PROMPT.md +++ b/docs/PLATFORM_P12_SESSION_PROMPT.md @@ -74,8 +74,8 @@ D39.165 §1) · эскроу П-18 · перф-строки `PD-412`/`PD-413`. При этом: - ⚠ **Монотонность флага (D39.153 §4б) НЕ отменяется** — пин стоит, правка пина под зелень запрещена (D39.121). Меняется другое: пожизненный счёт книги перестаёт выводиться из ЖИВОГО флага. -- ⚠ Ложный док-комментарий `recordWaveShape` — `internal/pgstore/sink.go:402-409`=`Monotone in one direction` - (лгущий абзац `:406-409`; редакция D39.165 писала `405-408` — дрейф) — переписать/снести целиком вместе +- ⚠ Ложный док-комментарий `recordWaveShape` — `internal/pgstore/sink.go`, греп `Monotone in one direction` + (⚠ номера строк сняты 30.08: файл правит эта же сессия; редакция D39.165 писала `405-408`, аудит нашёл `406-409` — дрейф) — переписать/снести целиком вместе с правкой поведения, не механически диапазон строк. - ⛔ **Заново НЕ предлагать** четыре отвергнутые формы: счёт всегда по редакторской волне · читать форму с последнего прогона · немонотонный флаг · «какая фаза считается» как вопрос клиенту. diff --git a/docs/architecture/12-go-style-notes.md b/docs/architecture/12-go-style-notes.md index 3eb14ebb..b99c533e 100644 --- a/docs/architecture/12-go-style-notes.md +++ b/docs/architecture/12-go-style-notes.md @@ -4,7 +4,7 @@ ## §0. Общность движка (директива владельца 24.07 — жёсткая) -1. **Движок language/book-agnostic.** Go-ЛОГИКА не ветвится по паре языков или книге. Пара-специфичное — ДАННЫМИ: `internal/lang` + `configs/langpacks/<пара>/` (fail-loud loader, байты в `pack.Version()` → снапшот). Книго-специфичное — данными КНИГИ: сид-глоссарий / brief / book.yaml. Книжный канон в общем пар-слое = утечка (прецедент: 古月 в surnames-compound.txt). +1. **Движок language/book-agnostic.** Go-ЛОГИКА не ветвится по паре языков или книге. Языко-специфичное — ДАННЫМИ в `internal/lang` + `configs/langpacks/`, и КЛЮЧ каталога называет ПРЕДМЕТ данных: `/` — факты исходного языка · `-/` — факты ПАРЫ (транслитерация, DC-чекеры, заголовки) · **`/` — то, что принадлежит ЦЕЛЕВОМУ языку независимо от источника** (появился 30.08 с `reader.txt` писателя книги, D39.175). ⚠ **Два режима, и путать их дорого:** данные, которые видит МОДЕЛЬ или гейт, читает `lang.Load` — их байты фолдятся в `pack.Version()` → снапшот, и правка = громкий `--resnapshot`; данные ЧИСТО РЕНДЕРА (читательские слова файла книги) читает отдельный загрузчик и в `Version()` НЕ фолдятся — иначе добавление слова в словарь перекупало бы каждую книгу. Отказ калибруется объявленностью: объявленный файл канала отсутствует ⇒ ГРОМКИЙ отказ; опциональный ⇒ фича инертна молча и версия байт-стабильна. Книго-специфичное — данными КНИГИ: сид-глоссарий / brief / book.yaml. Книжный канон в общем пар-слое = утечка (прецедент: 古月 в surnames-compound.txt). 2. **Пар-скоупнутые модули** (checkers_zh_ru и т.п.) легитимны как код за pair-гейтом, но их ДАННЫЕ (словники, руны, таблицы) подлежат выносу в langpack. 3. **Тест-данные пары легитимны**: zh/ru-строки в `*_test.go`/golden упражняют общую логику. Не путать с логикой. 4. **Промпты резолвятся КОНВЕНЦИЕЙ ПУТИ, а не ключом конфига** (форма пака-15; ⚠ прежняя редакция этой строки писала `prompts: {<пара>: файл}` — такого ключа нет, испр. 22.08 аудитом доков): пара-пак — `configs/pairs/<пара>.yaml`, роль — `prompts/<пара>/<роль>.md`, и пара без пакета падает ГРОМКО на загрузке с именем пути, который искали (`internal/config/pair.go`; пины `config.TestPromptResolvesByPairAndRoleConvention` и `config.TestPromptMissingPairPackFailsLoud`). ⚠ Прежний ключ не просто снят — он ОТВЕРГАЕТСЯ загрузкой (`config.TestRetiredPromptKeysAreRejected`), то есть конфиг, написанный по старой редакции этого норматива, не поднимется вовсе. Конвенции пары (меры, стихи, дискурс) живут в теле промпта СВОЕЙ пары — это данные. Дальний план — извлечение в структурированный пакет конвенций пары (10-prompt-architecture, гейтится формой пакета). diff --git a/docs/architecture/13-tech-debt-anchors.md b/docs/architecture/13-tech-debt-anchors.md index 3d792093..8c397e15 100644 --- a/docs/architecture/13-tech-debt-anchors.md +++ b/docs/architecture/13-tech-debt-anchors.md @@ -91,7 +91,7 @@ grep sanitiz по backend/configs/pipeline-c2.yaml — 0 вхождений (exi ### [CONFIRMED_LOST] Кандидат 73: tmctl «замена термина с главы N» -Команды НЕТ (⚠ перечень в этой улике был неполон и якорь уехал — испр. 22.08: полный список живёт ОДНИМ носителем в коде, `cmd/tmctl/main.go` `dispatchCommands` = `translate·report·status·export·redrive·manifest·backup·migrate·seed-lint`; прежняя редакция не знала `manifest`, `backup` и `migrate`); RedriveSelector выбирает только ФЛАГНУТЫЕ чанки по chapter/chunk/reason (pipeline/status.go:561-582), не «чанки с термом X». Источник кандидата — research/15-voice-and-state.md:200: одна команда ставит until_ch=N старой записи и since_ch=N новой. При этом СУБСТРАТ построен целиком: оконные строки в схеме (UNIQUE (book_id, src, sense, since_ch, until_ch), migrate.go:202) и $0-пере-пин при bank-only-сдвиге (pipeline/repin.go:14-36, D39.42 п.5/D39.45) — платятся только юниты, где инъекция реально изменилась. +Команды НЕТ (⚠ перечень в этой улике был неполон и якорь уехал — испр. 22.08 и 30.08 — перечень отставал ТРИЖДЫ, поэтому здесь он СНАПШОТ: сверяйся с носителем, не с этой строкой. Полный список живёт ОДНИМ носителем в коде, `cmd/tmctl/main.go` `dispatchCommands`, и на 30.08 включает также `bank-apply` (D39.158) и `build` (D39.175): = `translate·report·status·export·redrive·manifest·backup·migrate·seed-lint`; прежняя редакция не знала `manifest`, `backup` и `migrate`); RedriveSelector выбирает только ФЛАГНУТЫЕ чанки по chapter/chunk/reason (pipeline/status.go:561-582), не «чанки с термом X». Источник кандидата — research/15-voice-and-state.md:200: одна команда ставит until_ch=N старой записи и since_ch=N новой. При этом СУБСТРАТ построен целиком: оконные строки в схеме (UNIQUE (book_id, src, sense, since_ch, until_ch), migrate.go:202) и $0-пере-пин при bank-only-сдвиге (pipeline/repin.go:14-36, D39.42 п.5/D39.45) — платятся только юниты, где инъекция реально изменилась. **Фикс/скоуп:** Занесён правильно; в дизайн-паке скоупить как ТОНКУЮ CLI-обёртку над уже построенным (правка двух окон в сиде + штатный repin), не как пайплайн-работу. diff --git a/docs/architecture/15-money-path.md b/docs/architecture/15-money-path.md index fdabb8c7..8d58ac5c 100644 --- a/docs/architecture/15-money-path.md +++ b/docs/architecture/15-money-path.md @@ -11,6 +11,7 @@ - **Леджер SQLite.** Таблица `spend`, две фигуры: `committed_usd` (потрачено, с сырым ответом провайдера) и `reserved_usd` (зарезервировано под летящий вызов). `Reserve` перед каждым платным вызовом сверяет `SUM(committed_usd + reserved_usd)` книги и дня с потолками (`backend/internal/store/ledger.go:44,58,63`; Р7 — коммент `:12`); `Settle` конвертирует резервацию в committed и персистит ответ (`ledger.go:162,199-210`). Суммарно по книге — `SpentUSD` (`ledger.go:314-320`). - **Гейт потолков — ПЕР-ВЫЗОВНЫЙ, и был им до эмиттер-пака** (`Reserve` на каждый свежий attempt; прежняя формулировка «на границе юнита» была неверна по отношению к потолкам — опровергнута приёмкой D39.131). На границе юнита сидел РЕПЭЙР-суб-бюджет — ужесточён до пер-вызовного с ценой вызова (строка 135 закрыта D39.131); эскалационный кап хоп НЕ прицениваает — перелёт ≤1 хопа, задокументирован и запинен (диспозиция D39.131 п.2д, реопен — живой инцидент). - **leftover-reserved зануляется write-open.** `store.Open` (путь записи, каждый `translate`) выполняет `recoverReservations` (`backend/internal/store/store.go:88`, функция `:218`: `UPDATE spend SET reserved_usd = 0 WHERE reserved_usd > 0`) — файл владеется одним процессом, значит любой reserved на открытии принадлежит несеттлённому прогону. `OpenReadOnly` этого прохода намеренно НЕ делает (`store.go:95-104`) ⇒ **reserved из read-only `status` — всегда остаток мёртвого процесса** (несущий факт формулы PD-158, см. §3). +- **`--max-units` — ОБЪЁМНЫЙ потолок прогона, ортогональный денежному** (D39.165 §1б, принят D39.170). Ограничивает не деньги, а РАБОТУ: не больше N выходных ЮНИТОВ (гранулярность `units_total` манифеста — та же, в которой платформа продаёт главы) будет ОПЛАЧЕНО этим прогоном; юниты, отданные за $0 (резюм, ре-пин), ретраи и эскалации внутри юнита потолок не тратят. Принимает только `translate`. **Остановка по объёму — ЗАВЕРШЕНИЕ (exit 0), не пауза:** словарь кодов выхода не расширялся, различение живёт в отчёте прогона и в логе; отчёт разводит ДОСТАВКУ и ПЕРЕ-ДЕЛКУ. Носитель — `backend/internal/pipeline/volume.go`. ⚠ Проводка в платформу ГЕЙЧЕНА (`PD-422`): единственный писатель признака движения банка — дверь правок, рост АВТО-банка от майнинга флага не ставит. - **`--ceiling-usd` — КНИЖНЫЙ потолок, не бюджет прогона.** Дословно из флага: «the book USD ceiling in force for THIS RUN ONLY — it OVERRIDES book.yaml `ceilings.book_usd` and is never written back. It caps the book's CUMULATIVE committed+reserved spend, not this run's increment» (`backend/cmd/tmctl/invocation.go:114`; ⚠-коммент `main.go:110`). Ратификация — D39.122 п.2(в): пересчёт «пользовательский прирост → абсолют» — обязанность ПЛАТФОРМЫ, вторая денежная ось не заводится. День-потолок флаг НЕ перекрывает (PD-157). Проводка внутри: `Ceilings.BookUSD` в `Reserve` и именование сработавшего потолка в ошибке — `backend/internal/pipeline/stagerun.go:479-480,505-511` (стоп = `errReserveCeiling`). - **Потолки — wiring, не семантика:** `Ceilings` намеренно исключены из `BriefHash` («Wiring fields (paths, ceilings, db) deliberately excluded», `book.go:262-265`, канон `:267+`) ⇒ смена потолка не двигает ни снапшот, ни ре-билл (D39.110 п.2б). - **`status --json` отдаёт фигуры платформе:** `committed_usd`, `reserved_usd`, `book_ceiling_usd`, `ceiling_pct` = 100·(committed+reserved)/book_ceiling (`backend/internal/pipeline/status.go:155-158`). Дневной фигуры в status нет (PD-157). @@ -20,13 +21,13 @@ 1. **Грант.** `Grant` — строка леджера `kind='grant'`, идемпотентная по `(аккаунт, source, source_id)`: `on conflict (user_id, source, source_id) do nothing` (`platform/internal/pgstore/credits.go:38-49,357-362`). Фри-тир — тот же механизм в ТОЙ ЖЕ транзакции, что создание аккаунта (`pgstore/identity.go:86-88,153-155`; ⚠ **дефолт `SignupGrantMicroUSD` — НОЛЬ** (`platform/internal/config/config.go:262`), а не «5 × 1_000_000», как утверждала прежняя редакция: предложение «на бете дефолт в НОЛЬ» (D39.110 п.3) ИСПОЛНЕНО, `PD-104` в регистре стоит `fixed(P7)`. ⚠ Испр. аудитом доков 28.08 (D39.167) — ложная сумма стояла в ЕДИНСТВЕННОМ месте, куда идут за деньгами. 2. **Выбор потолка — шкала в ГЛАВАХ** (D39.110: от 1 до максимума, ноль не выбираем; потолок принадлежит ПРОГОНУ, в `book.yaml` не пишется). **Максимум = Balance КАК ЕСТЬ** (D39.115 п.2а; эррата в шапке D-лога `:4`): `Hold` — дебет в момент взятия, баланс уже не содержит открытых холдов, повторное вычитание вдвое укорачивает шкалу. Реализация: `pricing.Model.Scale` — `affordable = balance / PerChapter`, кламп по остатку глав, дефолт = верх шкалы (ратифицировано D39.123 п.2е; компромисс «весь баланс под одну книгу» принят осознанно) — `platform/internal/pricing/pricing.go:75-98`. -3. **Ставка $0.03/глава — конфиг-константа платформы:** `DefaultPerChapter = 30_000` micro-USD (`pricing.go:33`), переопределяется `PerChapterMicroUSD` (`config.go:90-93,194,229`). Провенанс сверен D39.123 п.2(г): exp08 v2 $0.0219/глава → ревизия D30.4 +15–25% → округление ВВЕРХ (низкая ставка опаснее высокой — прогон встанет на потолке посреди книги; `pricing.go:19-33`). Временная мера беты — честная оценка движковая (строка 166). +3. **Ставка $0.03/глава — конфиг-константа платформы:** `DefaultPerChapter = 30_000` micro-USD (`pricing.go:33`), переопределяется `PerChapterMicroUSD` (`config.go:90-93,194,229`). ⚠ **Провенанс ниже — ИСТОРИЯ, а не действующее обоснование (D39.165 §1, 28.08):** реальная цена главы замерена по движковым леджерам — средняя **$0.0115–$0.0190**, максимум одной главы $0.0375, то есть константа завышена в **1.6–2.6×**, и «купить 10 глав» отдаёт движку $0.30, покупающий ~16–26 глав («денежная ручка с подписью „главы"», `PD-410`). РЕШЕНО: цена — от ОБЪЁМА ИСХОДНИКА (носитель `chapters.units_total`, пишется интейком за $0), настоящий стоп — объёмный потолок В ДВИЖКЕ (см. §2). ⛔ Константу по сегодняшним числам НЕ калибровать (D39.165 ⛔г) — гейт строка 202. Исторический провенанс: exp08 v2 $0.0219/глава → ревизия D30.4 +15–25% → округление ВВЕРХ (низкая ставка опаснее высокой — прогон встанет на потолке посреди книги; `pricing.go:19-33`). Временная мера беты — честная оценка движковая (строка 166). 4. **Холд прироста ДО спавна.** `holdTx` пишет отрицательную строку `('hold', -amount)` и тем же знаком двигает кэш баланса (`credits.go:163-206,375-385`); нехватка — `ErrInsufficientCredit`. Холд + прогон + попытка + очередь — одна транзакция (`pgstore/runs.go:104` StartRun, `:338` рестарт; форма D39.123 п.2д). 5. **Аргумент движку: `bookCap = committed + прирост×ставка`, БЕЗ reserved — PD-158, дословно из кода: `return m.committed + increment`** (`platform/internal/runs/spawn.go:195-196`). Обе фигуры читаются ОДНИМ вызовом `status --json` перед стартом (`bookMeter`, `spawn.go:206-223`); отсутствие любой из них = отказ спавна («absent is not zero», PD-40). Почему без reserved: write-open движка занулит leftover до первой резервации, значит цифра из read-only status к моменту сравнения уже мертва; ратифицированная ранее формула с reserved переплачивала запасом ровно на leftover сверх холда — исполнено обеими формулами против гейта (D39.123 п.2б, поправка к D39.122(в)). Аргумент ≠ холд: он больше холда на committed (глоссарий `:50`). Фактически ушедшее значение хранится в `run_attempts.ceiling_arg_micro_usd` (PD-144, миграция 00011); потолок уходит argv-шаблоном `{{usd}}` (`config.go` `CeilingArg`, `spawn.go:132-150`); строка 145 закрыта D39.122. -6. **Baseline и списание.** `baseline = committed` книги на старте попытки (`spawn.go:74`); списание попытки = разница счётчика с baseline; счётчик ниже baseline = подменённая БД, сеттлится в ноль с WARN — сами цифры в лог не идут (D39.84; `reconcile.go:308-316`). +6. **Baseline и списание.** `baseline = committed` книги на старте попытки (`platform/internal/runs/spawn.go:82`=`baseline, bookCap := m.committed`); списание попытки = разница счётчика с baseline; счётчик ниже baseline = подменённая БД, сеттлится в ноль с WARN — сами цифры в лог не идут (D39.84; `reconcile.go:308-316`). 7. ⚠ **Терминальный вердикт ОПЕРАТОРА — путь холда мимо settle (добавлено 22.08, P8-FIX/D39.154).** Прогон, чей движок не ответит уже никогда, закрывает человек: `tmplatformctl run abandon --run --reason <текст> [--release-hold]`. Команда ОТКАЗЫВАЕТ, если попытка ещё называет юнит или несёт базовую линию траты — над живым движком прогон не закрывают. Холд возвращается ЦЕЛИКОМ в обоих случаях (к abandon допускается только попытка, не дошедшая до движка, значит она ничего не потратила), а флаг решает лишь КОГДА — сразу или на ближайшем свипе. До этого пака такого пути не было вовсе: холд чужого аккаунта висел, пока кто-нибудь не залезет в БД руками. -8. **Settle по фигуре движка.** `reconcile.settle` (`platform/internal/runs/reconcile.go:277+`) читает committed из status; `credits.Settle` одной транзакцией закрывает резервацию, возвращает холд и списывает spent; **spent > холда — капится холдом** с явной пометкой «capped at the hold; the engine reported …» (`credits.go:214-234`), перерасход движка счётом не абсорбируется. +8. **Settle по фигуре движка.** `reconcile.settle` (`platform/internal/runs/reconcile.go:1040`=`func (s *Service) settle(`) читает committed из status; `credits.Settle` одной транзакцией закрывает резервацию, возвращает холд и списывает spent; **spent > холда — капится холдом** с явной пометкой «capped at the hold; the engine reported …» (`credits.go:214-234`), перерасход движка счётом не абсорбируется. 9. **SpendBound (PD-159).** Отложенный settle читает пожизненный счётчик КНИГИ и без границы оплатил бы работу прогона-преемника (замер: $2.10 за прогон ценой $0.10, и преемник платил снова). Граница = наименьший baseline попыток той же книги, стартовавших ПОЗЖЕ: `committed = min(счётчик сейчас, SpendBound)` (`reconcile.go:293-307`; `pgstore/runs.go:268`). 10. **Открытое — эскроу/intent (строка 136, `docs/PROGRESS.md:85`):** write-ahead intent + состояние `uncertain`-эскроу + `closing`; холд НЕ освобождается по выходу юнита (сигнал обгоняет денежные события); spend не постится без свидетельства провайдера; расход сверх холда — полной суммой, превышение явной строкой убытка. Стройка по research/25, «скоро». До эмиттера (строка 103) платформа между спавном и settle слепа — защита держится холдом и потолком движка (§1). diff --git a/docs/architecture/17-seam-inbound-law.md b/docs/architecture/17-seam-inbound-law.md index db4fd49d..235329c8 100644 --- a/docs/architecture/17-seam-inbound-law.md +++ b/docs/architecture/17-seam-inbound-law.md @@ -95,8 +95,8 @@ | дверь | откуда следует | |---|---| | приём правок банка движковым глаголом | строка **199(а)** — развилка владения `book.yaml` растворяется этим | -| `--keys-file` (или иной явный путь к ключам) | строка **211** — блокер строки 202 | -| путь артефакта в ответе движка | строка **213** — снимает дубль у платформы | +| ~~`--keys-file`~~ **ПОСТРОЕН**: движок 27.08 (D39.158), передача с платформы 28.08 (P9/D39.162) | строка **211** ЗАКРЫТА и снята с бэклога; флаг — `backend/cmd/tmctl/invocation.go:145`=`keys-file`. ⚠ Строка **202** жива, но этим блокером больше не заперта | +| ~~путь артефакта в ответе движка~~ **ПОСТРОЕН** 28.08 (P9/D39.162), расширен 30.08 | конверт `StatusArtifacts` в `status --json`/`manifest --json`: `project_db` · `bank_export` · `mined_delta` · `mined_rejects` · **`book_files`** (пути собранной книги, D39.175). Строка **213** сужена до одной строки ДЕВ-супервизора | | `resnapshot --dry-run` | строка **124(в)**, глагол поименован там дословно. ⚠ Родня строки **231**: та просит ту же способность («оценить, ничего не покупая») со стороны ШВА, и решаться они, скорее всего, одним глаголом | | движковая оценка $/глава | строка **166**. ⚠ **Приоритет ПОДНЯТ 28.08:** цена главы измерена ($0.0115–0.0190 против константы продажи $0.03), и D39.165 §1 ратифицировал цену ОТ ОБЪЁМА исходника — оценка перестала быть удобством. Родня — строка **231** (глагол «свернуть банк и оценить ВНЕ прогона»): смету пере-прохода до покупки показать нечем, и это названная цена минора 0.7.0 | | ~~consent-флаги `--resnapshot`/`--accept-rebill` от платформы~~ **ПОСТРОЕНЫ 28.08 (D39.166)** | ⚠ гейт «192» СНЯТ владельцем (D39.165): открыть дверь к построенному механизму ≠ проектировать цикл пост-ридинга. Строка 199 закрыта, дверь продаётся членом `RunRequest.re_pass` (канон 0.7.0) |