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

16 KiB
Raw Blame History

17 — Закон входной двери шва движок ↔ платформа

СТАТУС: РАТИФИЦИРОВАНО D39.156 (слово владельца 23.08). Это НОРМА, ссылаться на неё можно. ⚠ ИСПОЛНЕН 27.08: первая дверь по этому закону ПОСТРОЕНА и залендженаtmctl bank-apply, четыре пака зоны бэкенда, ратификация D39.158 (лендинг d1eb8a9). Закон с этого дня читается не как план, а как норма, у которой есть работающий образец; онтология слоя, который дверь обслуживает, — 18-bank-ontology.md. Собрано оркестратором №19 по консилиуму со сторонним архитектором 2223.08.2026. Ратифицирована одной D-нотой; с этого момента двери встают паками без возврата к владельцу — ровно так, как код выхода 13 встал в ратифицированную полосу 1019 без нового решения ВЛАДЕЛЬЦА (D39.131 п.2а → D39.132 п.2г → D39.134: код 13 прошёл через направление и финализацию приёмкой, то есть через ратификации ОРКЕСТРАТОРА — именно это свойство норма и обещает).

Зачем понадобилось. Контракт фронт↔платформа ратифицирован (14-api-contract/), а контракт платформа↔движок — нет: исследование шва дошло до «события пишутся в events.jsonl» и дальше отвечало на вопрос «как запускать движок» (research/23, транспортная часть §1 superseded D39.106; research/25 — ратифицированная форма и отвергнутые альтернативы). В итоге НАРУЖУ из движка идёт богатый поток — журнал событий по курсору, полоса кодов выхода, exit-маркер, версионированные артефакты-проекции, — а ВНУТРЬ не идёт ничего, кроме запуска с флагами. Данные, которыми владеет пользователь продукта, движок принимать не умеет вообще, поэтому каждый внешний участник вынужден имперсонировать человека-оператора с текстовым редактором.

Почему НОРМА, а не каталог команд

За осознанный контракт говорят три прожитых инцидента гниения нератифицированных входных конвенций: дубль дефолта <book_id>.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:83-87=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:1222-1227=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 — нет. Закрыть.

  4. Незнакомый флаг = громкий отказ при спавне. Сегодня это так по устройству разбора аргументов (backend/cmd/tmctl/invocation.go:115=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(в), глагол поименован там дословно
движковая оценка $/глава строка 166; форма (поле в manifest --json против нового глагола) — за проектировщиком пака
consent-флаги --resnapshot/--accept-rebill от платформы строка 199(б), гейчена 192
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 этого механизма НЕТ вовсе (проверено по ЖИВОМУ телу architecture/05-decisions-log.md:239=ШОВ: прогон переживает деплой платформы; ⚠ испр. 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 — это ремонт, а не поллинг» подтверждено вхолодную независимыми панелями.