textmachine/docs/architecture/17-seam-inbound-law.md

126 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 17 — Закон входной двери шва движок ↔ платформа
> **СТАТУС: РАТИФИЦИРОВАНО D39.156 (слово владельца 23.08).** Это НОРМА, ссылаться на неё можно, и у неё есть
> работающий образец: первая дверь по этому закону ПОСТРОЕНА и заленджена 27.08 — `tmctl bank-apply` (D39.158).
> Онтология слоя, который дверь обслуживает, — `18-bank-ontology.md`.
>
> **Ратифицирована ДИСЦИПЛИНА, а не каталог команд**, и свойство этого выбора такое: новые двери встают паками
> без возврата к владельцу — ровно так, как код выхода `13` встал в ратифицированную полосу 1019 через
> ратификации ОРКЕСТРАТОРА (D39.131 п.2а → D39.132 п.2г → D39.134), а не новым решением владельца.
>
> **Зачем понадобилось.** Контракт фронт↔платформа ратифицирован (`14-api-contract/`), а платформа↔движок — нет:
> исследование шва дошло до «события пишутся в `events.jsonl`» и дальше отвечало только на вопрос «как запускать
> движок» (`research/23`, транспортная часть §1 superseded D39.106; `research/25` — ратифицированная форма и
> отвергнутые альтернативы). НАРУЖУ из движка идёт богатый поток — журнал событий по курсору, полоса кодов
> выхода, версионированные артефакты-проекции, — а ВНУТРЬ не шло ничего, кроме запуска с флагами: данные,
> которыми владеет пользователь продукта, движок принимать не умел вовсе.
## Почему НОРМА, а не каталог команд
За осознанный контракт говорят три прожитых инцидента гниения нератифицированных входных конвенций:
дубль дефолта `<book_id>.db` у платформы (строка бэклога **213**) · ручная константа версии API,
не поднятая при синке (D39.153) · три мины формата карты подписи, вскрытые только трассировкой
строки 199.
Против ПОЛНОГО каталога — то, что половина будущих дверей гейтится нерешёнными развилками: строка
**192** прямо запрещена к проектированию до полигонных итогов (D39.144 п.1; ⚠ область сужена D39.165 — открыть дверь к построенному ≠ проектировать цикл, см. ⚠-строку под таблицей дверей), **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:116`=`func TranslateArgs(workdir string, verifyBank bool`) · дефолт `project_db` по конвенции каталога
(`backend/internal/config/book.go:200`=`b.ProjectDB = filepath.Join(dir, b.BookID+".db")`) · конвенционное опциональное чтение `.auto-bank.yaml`
(`backend/internal/pipeline/mining.go:674`=`.auto-bank.yaml`).
2. **Посреди прогона внутрь идёт ТОЛЬКО сигнал.** `SIGTERM` = «дочти чанк и отпусти лок» — ⚠ **это ратифицированная ЦЕЛЬ, а не сегодняшнее дерево (пометка 10.09).** Владелец ратифицировал две остановки (`D39.234` п.1а): первый сигнал — мягкий, он и означает «дочти и отпусти». В КОДЕ его ещё нет: `backend/cmd/tmctl/main.go:207` отменяет контекст прогона, летящий вызов рвётся, книжится по оценке и метится `cancelled`. ⇒ норму НЕ переписывать под дерево, дерево догоняет норму; носитель работы — строка бэклога **381**, где этот адрес назван третьим. Всё
остальное — на границах процессов. Арбитр — эксклюзивный flock проекта на всю жизнь прогона
(`backend/internal/store/store.go:55`=`EXCLUSIVE flock on`), мутирующий глагол берёт его неблокирующе и при занятости
отказывает **ровно классом 12** (`platform/internal/ingest/exit.go:69`=`ExitProjectLocked = 12`), а НЕ 10:
полоса различает их не косметически — 10 (`platform/internal/ingest/exit.go:62`=`ExitConfigInvalid = 10`) значит «деплой сломан,
чинит человек, книги ждут», 12 значит «ничего не произошло, подожди и повтори» (D39.134 п.2а). Запись через слэш
позволила бы исполнителю выбрать 10, и платформа отнесла бы занятый лок к операторским ошибкам.
*Почему жёстко:* движок пере-читает сид ПОСРЕДИ прогона (`mining.go:221`), запись в живой прогон
въехала бы в снапшот недетерминированно. Потолок посреди прогона менять НЕЛЬЗЯ — он едет со
стартом и после не меняется, лекарство — новый прогон
(`platform/internal/runs/reconcile.go` (греп `202` в обработчике возобновления; ⚠ номер строки снят 30.08 — файл правит живая сессия P12)). Дверь «менять потолок файлом» закрыта;
не выдумывать её заново из `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`).
4. **Незнакомый флаг = громкий отказ при спавне.** Сегодня это так по устройству разбора аргументов
(`backend/cmd/tmctl/invocation.go:128`=`flag.NewFlagSet(cmd, flag.ContinueOnError)`); зафиксировать законом, а не полагаться на случайность.
5. **Глагол зовётся ТЕМ ЖЕ пиннутым бинарём, что и возобновляемая попытка** (`runs/spawn.go:250-258`=`engineBinary is the path an attempt is PINNED to`,
строка бэклога 139). Иначе файл, записанный новым бинарём, читается старым — а при нестрогом
загрузчике сида (строка **212**) новое поле теряется МОЛЧА (⚠ **адрес испр. 10.09: строки 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`.
⚠ Предположение, не механизм: окно «старая платформа + новый движок» считается покрытым обратной
совместимостью флагов. Если деплои разъедутся сильнее — вернуться к вопросу.
## Двери, УЖЕ подразумеваемые живыми строками
| дверь | откуда следует |
|---|---|
| `resnapshot --dry-run` | строка **124(в)**, глагол поименован там дословно. ⚠ **Испр. 10.09: ссылка на «родню строки 231» СНЯТА — такой строки в живом трекере нет,** а обоснование стояло на ней и на слове «скорее всего», то есть на догадке без хозяина. Прежний текст: родня строки 231 просит ту же способность («оценить, ничего не покупая») со стороны ШВА, и решаться они, скорее всего, одним глаголом |
| ✅ **И ПРОЧИТАНА платформой с 06.09** — механика ОДНИМ носителем: `15-money-path.md` §3 (акт `D39.208`). Прежняя редакция: ⚠ движковая оценка $/глава — **ПОСТРОЕНА и опубликована 05.09** (`expected_usd` · `book_once_usd` · `step_max_usd` в `manifest --json`/`status --json`, лендинг `81a89e9`, акт D39.206); непостроена ПЛАТФОРМЕННАЯ половина — числа никем не читаются (`git grep -lE "StepMax|step_max" -- platform/` пусто) | строка **166**. Приоритет поднят: D39.165 §1 ратифицировал цену ОТ ОБЪЁМА исходника, и оценка перестала быть удобством. ⚠ Числа цены главы и их пере-пин под цены с 16.08 — только `15-money-path.md` §3 п.3, здесь копии нет |
| `tmctl init` (форма В) | строка **170**; приоритет ПАДАЕТ после двери 199(а) — та забирает её главный довод |
**ПОСТРОЕНЫ по этому закону и из таблицы сняты** (форма читается по ним как по образцам): приём правок банка движковым глаголом `tmctl bank-apply` — 27.08, D39.158, развилка владения `book.yaml` (строка 199а) растворена · `--keys-file` — движок 27.08 (D39.158), передача с платформы 28.08 (P9/D39.162), строка **211** ЗАКРЫТА и блокером живого прогона больше не является (⚠ ⚠ испр. 05.09: строка **202** ЗАКРЫТА D39.200 — прогон отработал; её остаток о непокрытых поверхностях живёт строкой **255**) · путь артефакта в ответе движка — конверт `StatusArtifacts` в `status --json`/`manifest --json` (`project_db` · `bank_export` · `mined_delta` · `mined_rejects` · `book_files`, D39.162/D39.175), строка **213** сужена до одной строки ДЕВ-супервизора · consent-флаги `--resnapshot`/`--accept-rebill` от платформы — 28.08, D39.166, продаются членом `RunRequest.re_pass` (канон контракта 0.7.0+); гейт «строка 192» СНЯТ владельцем (D39.165).
**Не установлено, проверить при постановке:** строка **198** (апгрейд стирает замечания) — возможно, подразумевает
глагол пере-анонса или бэкфилла, потому что движок анонсирует юнит один раз за жизнь книги
(`backend/internal/pipeline/events.go:435`=`func unitOnceKey(bookID string, k unitWave) string`); направление фикса консилиум НЕ установил.
**Из чего двери НЕ следует** (экономия паков): **255** (бывшая **202**, закрыта D39.200) — потребитель всего набора, своих команд не
несёт · ~~**208**~~ · ~~**210**~~ (⚠ **испр. 10.09: обеих строк в живом трекере НЕТ, закрыты; довод «дверь из них не следует» остаётся, номера сняты**) · вайр-батч 182 ·
**164** — не команда, а починка: смета уже едет в `status --json`.
## Чего НЕ строить
- **Инбокс-каталог входящих команд** как зеркало `events.jsonl`. Все трассированные нужды садятся на
границы процессов; единственный мид-ран кандидат закрыт каноном (п.2); инбокс-файл возвращает
гонку, ради исключения которой существует flock.
**Эррата, которую ратифицирующая нота обязана поставить на `research/25`:** там «поднятие потолка файлом через
atomic rename» отнесено к носителям D39.106 — в теле D39.106 этого механизма НЕТ вовсе (проверять по ЖИВОМУ телу ноты **D39.106** («ШОВ: прогон переживает деплой платформы») в `architecture/05-decisions-log.md`, адресуя НОМЕРОМ, а не строкой; ⚠ в слайсе
`archive/architecture/05-decisions-D39-106-123.md` тела D39.106 НЕТ — греп по нему подтвердил бы эррату вакуумно). Строка была КАНДИДАТОМ и
никогда не ратифицировалась, а живой канон решил ось иначе (п.2).
- **Пере-открытие транспорта** (gRPC/сокет/брокер/пуш) — `research/25`, у каждой альтернативы 0
голосов из ~18, решение владельца принято.
- **Общий Go-пакет типов на две системы** — сварка независимых деплоев против D39.81/85; перевод
словаря на шве — сознательный контракт, а не недоделка.
- **Быстрый poll статуса** — `status` дорог по построению (пере-ингест книги), свежесть даёт журнал
событий; «status — это ремонт, а не поллинг» подтверждено вхолодную независимыми панелями.