# 17 — Закон входной двери шва движок ↔ платформа > **СТАТУС: РАТИФИЦИРОВАНО D39.156 (слово владельца 23.08).** Это НОРМА, ссылаться на неё можно. ⚠ **ИСПОЛНЕН 27.08: первая дверь по этому закону ПОСТРОЕНА и заленджена** — `tmctl bank-apply`, четыре пака зоны бэкенда, ратификация D39.158 (лендинг `d1eb8a9`). Закон с этого дня читается не как план, а как норма, у которой есть работающий образец; онтология слоя, который дверь обслуживает, — `18-bank-ontology.md`. Собрано оркестратором №19 по консилиуму > со сторонним архитектором 22–23.08.2026. Ратифицирована одной D-нотой; с этого момента двери встают паками без > возврата к владельцу — ровно так, как код выхода `13` встал в ратифицированную полосу 10–19 без > нового решения ВЛАДЕЛЬЦА (D39.131 п.2а → D39.132 п.2г → D39.134: код 13 прошёл через направление и > финализацию приёмкой, то есть через ратификации ОРКЕСТРАТОРА — именно это свойство норма и обещает). > > **Зачем понадобилось.** Контракт фронт↔платформа ратифицирован (`14-api-contract/`), а контракт > платформа↔движок — нет: исследование шва дошло до «события пишутся в `events.jsonl`» и дальше > отвечало на вопрос «как запускать движок» (`research/23`, транспортная часть §1 superseded > D39.106; `research/25` — ратифицированная форма и отвергнутые альтернативы). В итоге НАРУЖУ из > движка идёт богатый поток — журнал событий по курсору, полоса кодов выхода, exit-маркер, > версионированные артефакты-проекции, — а ВНУТРЬ не идёт ничего, кроме запуска с флагами. Данные, > которыми владеет пользователь продукта, движок принимать не умеет вообще, поэтому каждый внешний > участник вынужден имперсонировать человека-оператора с текстовым редактором. ## Почему НОРМА, а не каталог команд За осознанный контракт говорят три прожитых инцидента гниения нератифицированных входных конвенций: дубль дефолта `.db` у платформы (строка бэклога **213**) · ручная константа версии API, не поднятая при синке (D39.153) · три мины формата карты подписи, вскрытые только трассировкой строки 199. Против ПОЛНОГО каталога — то, что половина будущих дверей гейтится нерешёнными развилками: строка **192** прямо запрещена к проектированию до полигонных итогов (D39.144 п.1), **161**/**185** ждут дизайн-пака, **170** — «после беты формы Б». Каталогизировать их сейчас значит выдумывать. Прецедент, разрешающий спор, — исходящее направление: там выиграла ратификация ПОЛОСЫ и ДИСЦИПЛИНЫ, а не перечня. ## Закон: семь пунктов 1. **Вход — это аргументы при спавне и файлы, названные аргументами.** Данные, которыми владеет пользователь продукта, доезжают движковым ГЛАГОЛОМ; движок — единственный писатель файлов своей схемы и владелец их путей и конвенций. Платформа читает только версионированные артефакты по путям, которые ей СООБЩЕНЫ ДВИЖКОМ **ИЛИ РАТИФИЦИРОВАНЫ КОНТРАКТОМ ШВА**, и никогда не выводит путь сама. ⚠ **Два выноса, без которых буква закона объявляет вне закона уже ратифицированное:** **(а) форма Б** — одноразовый рендер стартового `book.yaml` при рождении книги (D39.130 п.2б): платформа пишет пять ключей идентичности, всё остальное проходит из шаблона непрочитанным (`platform/internal/books/render.go:29-33`=`comes from the template unread and unjudged`); живёт до формы В (строка 170). **(б) фикс-имена ратифицированной конвенции шва** — `events.jsonl` никто платформе не «сообщает», это константа (`platform/internal/ingest/tail.go:17`=`const JournalFile = "events.jsonl"`, D39.106); без этого выноса буква ломает главный канал самого шва. *Прецеденты:* потолок аргументом (D39.110 п.2б → `--ceiling-usd`, D39.122) · `--verify-bank` флагом (`platform/internal/runner/engine.go:99`=`func TranslateArgs(workdir string, verifyBank bool`) · дефолт `project_db` по конвенции каталога (`backend/internal/config/book.go:189`=`b.ProjectDB = filepath.Join(dir, b.BookID+".db")`) · конвенционное опциональное чтение `.auto-bank.yaml` (`backend/internal/pipeline/mining.go:617`=`.auto-bank.yaml`). 2. **Посреди прогона внутрь идёт ТОЛЬКО сигнал.** `SIGTERM` = «дочти чанк и отпусти лок»; всё остальное — на границах процессов. Арбитр — эксклюзивный flock проекта на всю жизнь прогона (`backend/internal/store/store.go:55`=`EXCLUSIVE flock on`), мутирующий глагол берёт его неблокирующе и при занятости отказывает **ровно классом 12** (`platform/internal/ingest/exit.go:58`=`ExitProjectLocked = 12`), а НЕ 10: полоса различает их не косметически — 10 (`platform/internal/ingest/exit.go:51`=`ExitConfigInvalid = 10`) значит «деплой сломан, чинит человек, книги ждут», 12 значит «ничего не произошло, подожди и повтори» (D39.134 п.2а). Запись через слэш позволила бы исполнителю выбрать 10, и платформа отнесла бы занятый лок к операторским ошибкам. *Почему жёстко:* движок пере-читает сид ПОСРЕДИ прогона (`mining.go:221`), запись в живой прогон въехала бы в снапшот недетерминированно. Потолок посреди прогона менять НЕЛЬЗЯ — он едет со стартом и после не меняется, лекарство — новый прогон (`platform/internal/runs/reconcile.go:1437`=`answering 202 with the run in the state`). Дверь «менять потолок файлом» закрыта; не выдумывать её заново из `research/25`. 3. **Каждый JSON-выход глагола несёт версию документа.** Сегодня асимметрия: `tm-bank-v1` (`pipeline/bankexport.go:30`=`const bankExportVersion = "tm-bank-v1"`) и `tm-manifest-v2` (`pipeline/manifest.go:46`=`const manifestVersion = "tm-manifest-v2"`) версию несут, а `status --json` — ⚠ **УЖЕ НЕСЁТ, п. ИСПОЛНЕН** (`backend/internal/pipeline/status.go:155-156`=`const statusVersion = "tm-status-v1"`, проставляется `:485`). Асимметрии больше нет; прежняя редакция требовала «Закрыть» и заставила бы следующий пак заплатить за построенное второй раз — испр. аудитом доков 28.08 (D39.167). 4. **Незнакомый флаг = громкий отказ при спавне.** Сегодня это так по устройству разбора аргументов (`backend/cmd/tmctl/invocation.go:128`=`flag.NewFlagSet(cmd, flag.ContinueOnError)`); зафиксировать законом, а не полагаться на случайность. 5. **Глагол зовётся ТЕМ ЖЕ пиннутым бинарём, что и возобновляемая попытка** (`runs/spawn.go:240-248`=`engineBinary is the path an attempt is PINNED to`, строка бэклога 139). Иначе файл, записанный новым бинарём, читается старым — а при нестрогом загрузчике сида (строка **212**) новое поле теряется МОЛЧА. 6. **Словарь движка ↔ словарь контракта — одна таблица, два направления, один файл.** Сегодня перевод односторонний, на чтении (`platform/internal/ingest/bank.go:15-21`=`Every vocabulary crossing here is TRANSLATED and none is passed through`). Запись делает таблицу двунаправленной, и две копии разъедутся. 7. **Мутирующий глагол сначала даёт проекцию, потом мутирует.** «Почём ДО подтверждения». *Прецеденты:* rebill-смета с порогом согласия (D20.2-Q2, `pipeline/rebill.go`) · строка 185 («сколько глав и почём до подтверждения») · `--dry-run` строки 124(в). ## Совместимость без второго рукосинхронизируемого контракта Версий у КОМАНД нет и заводить их не нужно: совместимость выводится из трёх уже существующих ног — пиннинг попытки к бинарю (строка 139) · незнакомый флаг роняет спавн громко (п.4) · версионированный конверт на каждом JSON-выходе (п.3). Плюс одна асимметричная норма: движок не меняет семантику флага или поля без мажора, новые флаги едут «движок-первым» — расширение уже ратифицированного порядка деплоя `migrate` (D39.134 п.3). Хендшейк или `capabilities`-глагол движка — НЕ строить: один хост, «least mechanism» (`12-go-style-notes.md` §1), а судьбу ручной константы показал инцидент `ContractVersion`. ⚠ Предположение, не механизм: окно «старая платформа + новый движок» считается покрытым обратной совместимостью флагов. Если деплои разъедутся сильнее — вернуться к вопросу. ## Двери, УЖЕ подразумеваемые живыми строками | дверь | откуда следует | |---|---| | приём правок банка движковым глаголом | строка **199(а)** — развилка владения `book.yaml` растворяется этим | | `--keys-file` (или иной явный путь к ключам) | строка **211** — блокер строки 202 | | путь артефакта в ответе движка | строка **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) | | `tmctl init` (форма В) | строка **170**; приоритет ПАДАЕТ после двери 199(а) — та забирает её главный довод | **Не установлено, проверить при постановке:** строка **198** (апгрейд стирает замечания) — возможно, подразумевает глагол пере-анонса или бэкфилла, потому что движок анонсирует юнит один раз за жизнь книги (`backend/internal/pipeline/events.go:399-400`=`func (e *emitter) unitOnceKey(k unitWave) string`); направление фикса консилиум НЕ установил. Записано третьей категорией, чтобы читатель ратификации не счёл свип дырявым. **Из чего двери НЕ следует** (экономия паков): **202** — потребитель всего набора, своих команд не несёт · **208** — фикс транспорта внутри движка · **210** — развилка данных/промпта, вайр-батч 182 · **164** — не команда, а починка: смета уже едет в `status --json`. ## Чего НЕ строить - **Инбокс-каталог входящих команд** как зеркало `events.jsonl`. Все трассированные нужды садятся на границы процессов; единственный мид-ран кандидат закрыт каноном (п.2); инбокс-файл возвращает гонку, ради исключения которой существует flock. ⚠ **Эррата, которую ратифицирующая нота обязана поставить на `research/25`:** там «поднятие потолка файлом через atomic rename» отнесено к носителям D39.106 — в теле D39.106 этого механизма НЕТ вовсе (проверено по ЖИВОМУ телу ноты **D39.106** («ШОВ: прогон переживает деплой платформы») в `architecture/05-decisions-log.md` — адресуем НОМЕРОМ, а не строкой: прежний line-якорь уже съехал внутрь чужого тела (гейт якорей, 28.08); ⚠ испр. 23.08 — прежняя редакция ссылалась на слайс `archive/architecture/05-decisions-D39-106-123.md`, где тела D39.106 НЕТ, и греп по нему подтвердил бы эррату вакуумно; первый хит темы — только в D39.110). Строка была КАНДИДАТОМ и никогда не ратифицировалась, а живой канон решил ось иначе (п.2). - **Пере-открытие транспорта** (gRPC/сокет/брокер/пуш) — `research/25`, у каждой альтернативы 0 голосов из ~18, решение владельца принято. - **Общий Go-пакет типов на две системы** — сварка независимых деплоев против D39.81/85; перевод словаря на шве — сознательный контракт, а не недоделка. - **Быстрый poll статуса** — `status` дорог по построению (пере-ингест книги), свежесть даёт журнал событий; «status — это ремонт, а не поллинг» подтверждено вхолодную независимыми панелями.