| .. | ||
| cmd/tmctl | ||
| configs | ||
| docs | ||
| example | ||
| internal | ||
| prompts/zh-ru | ||
| .env.example | ||
| go.mod | ||
| go.sum | ||
| README.md | ||
backend/ — Go-бэкенд TextMachine
Зона сессии «Бэкенд». Контракт — docs/architecture/05-decisions-log.md (свежая голова — с хвоста файла); целевая архитектура (7 слоёв, инвариант общности §0.1 — любая книга/пара/структура; статус стройки — шапка-таблица) — docs/architecture/09-target-architecture.md; норматив общности — 12-go-style-notes.md; актуальный сессионный промт — список «Активные хендофф-промты» в docs/README.md; контракты Фазы 0 — 03-implementation-notes.md (через баннер); квирки провайдеров — docs/experiments/00-provider-quirks.md (читать ПЕРЕД правкой адаптеров/вызовами). Язык-данные майнера — вне пайплайна: internal/lang+configs/langpacks/ (D39.16); пар-калибровки — configs/pairs/<пара>.yaml, промпты — prompts/<пара>/<роль>.md по конвенции (D39.23). Подсистемы (chunk/miner/checks/membank) не импортируют друг друга и не импортируют pipeline — драйвер является композит-корнем (D39.23; проверяется go list -deps). .env не читать.
(Актуализировано оркестратором 01.08 по мандату владельца, факты сверены по коду; предыдущая ревизия 17.07 — эры D39.16, до паков 16–20 и фазы 2 общности D39.64.)
Карта пакетов
| Пакет | Что делает | Ключевые файлы |
|---|---|---|
cmd/tmctl |
CLI: translate / status (read-only N/M+паспорта глав+деньги, --json; работает ПРИ живом прогоне — store без flock) / report (request-log+леджер книги + per-run quality-report: структурный KPI предл./нарратив-абзац, тире, cosmetic-strip/echo-rates, глоссарий-промахи, trust-gated — наблюдаемость, НЕ гейт; D39.2-T4) / export (экспорт-поверхность для полигона: final_hash→checkpoint→checks.ExportNormalize, manifest-join c pending/ghost-учётом, ConfigDrift-поле, --plaintext; D39.5) / redrive (переатака флагнутых: --chapter/--chunk/--reason/--dry-run, D15.3). main — тонкая обвязка: разбор/exit-коды/.env/рендеры в тестируемых функциях |
main.go, invocation.go, render.go, dotenv.go |
internal/llm |
OpenAI-совместимый транспорт + retry/backoff, capability-слой (budget_field/temperature/reasoning per-модель), провайдеры openai/local (no-proxy)/anthropic (DEPRECATED-референс); failover.go удалён паком-17 — маршрутизация лейблами живёт в config/pipeline (канал B, один хоп chain[0], fail-closed) |
httpllm.go, capability.go, provider_*.go |
internal/ledger |
Цены по usage (вкл. reasoning/cache-поля), PriceForResponse по фактической модели |
pricing.go |
internal/store |
SQLite (modernc, CGO-free), цепочка миграций schema_version (⚠ ALTER-шаги v8+ не идемпотентны вопреки шапке — бэклог-строка 49а), reserve/settle+checkpoint, chunk_status, глоссарий (+подписной цикл терминолога), ruby, retrieval_state, request_log; OpenReadOnly — без flock/миграций/recovery для status/report/export |
ledger.go, migrate.go, glossary.go, store.go |
internal/config |
fail-fast загрузка models/pipeline/book; эхо-мина-гейт echoMineViolation; CheckRunnable блокирует неисполнимое (C2/fanout/judge); пар-слой (D39.23): configs/pairs/<пара>.yaml — калибровка нарезки + коридор длины пары; промпт-резолв КОНВЕНЦИЕЙ prompts/<пара>/<роль>.md из Book.LangPair()+роли, fail-loud на пару без пакета (ja-книга не едет молча через zh-промпт), осознанное исключение — prompt_override на стадии |
models.go, pipeline.go, pair.go |
internal/text |
Нормализация и рун-примитивы, общие для банка/майнера/чекеров (script-agnostic слой, lang НЕ импортирует): NormalizeSourceKey/NormalizeTargetForm (версионированный артефакт NormVersion(), фолдится в снапшот), NormalizeSource, TokenizeScript (алфавит цели из данных, Mn-фолд U+0301; TokenizeCyrillic = ru-дефолт-обёртка), DenseScript — единый дом sizing-таксономии (D39.64) |
norm.go, runes.go, source.go |
internal/seed |
Схема сид-YAML книги (File/Term/Decl/Alias) — общий артефакт: майнер её ПИШЕТ (mined-дельта), банк ЧИТАЕТ (LoadGlossarySeed); тип общий, поэтому эмиссия не может разъехаться с загрузчиком |
seed.go |
internal/lang |
Языковые ДАННЫЕ и пак-механизм: эмбед-плоскость data/*.txt (хешируется в EmbeddedVersion() → снапшот, D39.64) · langpack Load/книго-overlay (аллоулист файлов, fail-loud) · манифест-по-каналам (manifest.txt источника объявляет source-morphology/transliteration; манифест-less пак = все каналы, байт-идентичен) · единый реестр письменностей script.go (LangScripts/IsCJKScriptLang из lang-script.txt) · реестр целей из data/target-<tgt>.txt |
langpack.go, embedded.go, script.go |
internal/terminology |
Роль терминолога (пак-20): смердженный банк + KWIC-контексты → батчи консолидации dst той же дешёвой моделью; языковой экран target_script; durable-деньги подписного цикла |
terminology.go, script.go |
internal/chunk |
Ингест (txt/epub, кодировки, ruby) + чанкер: Ingest/IngestEncoded → Document, SplitChunks → []Chunk по SegBudget; структура источника (главы, CJK-числительные, heading-правило) — ДАННЫЕ из lang |
ingest.go, chunker.go, chunktest/ |
internal/membank |
Банк памяти v2, memmatch-v4: Aho-Corasick, спойлер-окна, disposition, trust-gated longest-match (draft-длиннее НЕ съедает вложенный approved; отказ = громкая телеметрия — терм-дрейф закрыт в КОДЕ, D39.2-T1), post-check, рендереры инъекции, загрузка/валидация сида (SeedLint) |
memory.go, memseed.go, mempostcheck.go |
internal/miner |
Детерминированный оффлайн-майнер банка (WS3, $0): MineBank над нормализованными чанками + контраст-корпус → []Term, DeltaYAML — сид-дельта на подпись владельца; все пар-данные приходят *lang.Pack |
miner.go, miner_*.go |
internal/checks |
Детерминированные $0-вердикты над текстом чанка, target-aware ПО ДАННЫМ (D39.64): Checkers компилируется из цель-данных, TargetActive()/TargetScriptNonLatin() гейтят всё — цель без данных инертна, Go-веток по паре нет: санитайзер v7 (fold-first, CJK/Hangul-leak, CJK-глосс-whitelist) + ExportNormalize (export-contract слой 6, no-op без цель-данных), cheap-гейты и DC-чекеры (пар-данные из пака), coverage-гейт эксцизии, regression-guard. Флаг-вокабуляр НЕ импортирует: возвращает факт, диспозицию назначает драйвер |
sanitizer.go, cheapgates.go, checkers.go, coverage.go |
internal/pipeline |
Композит-корень (драйвер): сетап runner.go → снапшот snapshot.go → сид seeding.go → цикл книги bookrun.go → волны waverun.go → петля чанка chunkrun.go → стадия stagerun.go → эскалация с ре-гейтом escalation.go → resume resume.go; classify/disposition; реестр roleInjectionRenderers (новая роль = данные+рендерер, не правка switch); export.go/quality.go/status.go — read-only проекции (общая derivation member-drop = memberDrops) |
bookrun.go, waverun.go, stagerun.go, export.go, disposition.go |
internal/obs |
trace_id, структурные логи (contextHandler несёт book/chapter/chunk/stage/role из ctx), safego |
Инварианты — ЛОМАТЬ НЕЛЬЗЯ (каждый закреплён тестами)
- Деньги: reserve → call → settle+checkpoint одной транзакцией;
committed == SUM(checkpoints)переживает kill -9 (kill9_test.go); цена — по фактически ответившей модели; потолки книга/день считают committed+reserved. Одно исключение — явныйtmctl redrive(D15.3): после негоcommitted ≥ SUM(checkpoints)— безопасное направление. - Snapshot-дисциплина: всё, что влияет на wire-байты ИЛИ вердикты (capability, память content-hash +
memoryMatchVersion, context-assembly, версии classify/coverage/chunker/maxtok/sanitizer (только при enabled), эмбед-данныеlang.EmbeddedVersion()(D39.64), эскалационные оверрайды, биндинг пара→промпт через BriefHash+PromptSHA256), свёрнуто вsnapshotID; правка = громкий--resnapshot= переоплата книги (D15). Новый фолд обязан быть УСЛОВНЫМ по присутствию данных (правило §6.2, D39.60). - Эхо-мина DeepSeek: thinking НИКОГДА не отключать —
reasoning:"off"там осознанный no-op;echoes_when_thinking_off+ рекурсивный скан extra_body валят конфиг fail-fast. У grok off = ЯВНЫЙreasoning_effort:"none". - Порядок classify: refusal-blacklist / эхо источника (
sourceScriptShareпо ОБЪЯВЛЕННОМУ письму, D39.64) — ДО length/empty; retry-flagged только {length, empty} на той же модели; loop-чек ПЕРЕД удвоением max_tokens. Санитайзер — ТОЛЬКО на финальной стадии (isFinal) и только при активной цели (TargetActive). - Эскалация: ровно 1 хоп, результат РЕ-гейтится, retry-бюджет не сбрасывается, editor pinned, канал adult — только permissive (fail-closed).
- Детерминизм: рендер — чистая функция snapshot; сортировки перед записью; никакого map-order в выводе; length-prefixed хэши; сорс-чанкер lossless (fuzz).
- Нейтральность адаптера:
internal/llmне знает про перевод — контент-вердикты в раннере (disposition-слой, post-settle). - Экспорт-контракт (D39 слой 6, D39.5):
checks.ExportNormalize— эфемерная проекция (НЕ входит в request_hash/чекпоинты/деньги), применяется к КАЖДОМУ финальному тексту на всех выдающих поверхностях; любая внешняя экстракция (полигон!) обязана читатьtmctl export, не сырые чекпоинты store — иначе видит ненормализованные байты и молча-неполную книгу (export делает manifest-join + drift-гард, сырое чтение — нет).
Подготовка ja-книги (чек-лист сида)
- Kana-написания имён = matchable. Ruby-чтения захватываются на инжесте и авто-подцепляются как alias ручной записи (D16.4,
attachRubyAliasesToManual). Имя, встречающееся ТОЛЬКО каной, внеси вaliases:руками — иначе «тихо пусто». - Двойные чтения (強敵→とも) — под флагом: длинное двойное чтение может дать CONFIRMED-ложь — при подозрении убери из сида. Полная дизамбигуация — Ф2 (B6).
- Kana-precision ≥4 не замерена (minKeyLenPhonetic=3): кана-ключи не проверяются на границу слова (нет сегментации) — задача полигона + токенизатор B6.
- ⚠ ja-книга требует каталога
prompts/ja-ru/(по файлу на роль) — промпт-резолв конвенцией fail-loud'ится без него, называя ожидаемый путь (пример: закомментированный ja-блок вexample/book.yaml). Контент пакета = данные под реальную книгу (инвариант общности §0.1), не сочинять.
Как гонять
Стенд: WSL2 (localhost из-под прокси = 403 — для local-вызовов no-proxy транспорт). Книга-стенд: /home/ubuntu/books/gu-zhenren/ (GB18030; тексты и производные ВНЕ git; парити-тесты майнера читают отсюда под TM_MINER_PARITY=1).
go build ./... && go vet ./... && go test ./... -race # всё зелёное = норма
go run ./cmd/tmctl translate --config example/book.yaml # реальные вызовы — ключи в .env (пример: zh→ru)
go run ./cmd/tmctl status --config example/book.yaml --json # $0, N/M+паспорта+деньги, живой прогон ок
go run ./cmd/tmctl report --config example/book.yaml # $0, quality-report (KPI/rates)
go run ./cmd/tmctl export --config example/book.yaml # $0, экспорт-JSON для полигона (--plaintext для человека)
# live-conformance (реальные провайдеры, платно, вне CI):
set -a; . ./.env; set +a; TM_LIVE=1 go test -tags live -run TestLive -v ./internal/pipeline/
Golden-гард детерминизма (golden_test.go + testdata/golden/): пинит бит-в-бит snapshotID, request_hash, wire-тела, вердикты и resume-байты. Красный golden = wire/вердикты изменились = --resnapshot = переоплата. Обновлять ТОЛЬКО на ратифицированной смене поведения: TM_UPDATE_GOLDEN=1 go test ./internal/pipeline/ -run TestGolden; при re-capture — маскированный структурный дифф (хеши/версии → плейсхолдер) обязан быть пустым, если смена версий не меняла вердиктов.
Финал каждой вехи — агентское адверсариальное селфревью (мандат CLAUDE.md 12.07); внешнее ревью — оркестратор.
Известный техдолг (не трогать молча; ЕДИНЫЙ трекер — таблица «Бэклог» в docs/PROGRESS.md, номера строк = она)
- D15.2: READ-половина
tmctl exportпостроена (D39.5); annotation/override-половина и content-addressed resume отложены (строка 49). F3 at-most-once — после D15.2 (строка 50). RetriesНЕ фолдится в снапшот (pre-existing, D39.4-LOW; сознательно так же для ручкиRegenerateEchoBeforeEscalate, D39.64 — дефолт 0 = текущее поведение): в крэш-окне пониженный ретрай-бюджет молча бросает оплаченные OK-чекпоинты.- ⚠ ALTER-шаги миграций v8+ НЕ идемпотентны вопреки шапке
migrate.go— полу-применённая БД не сходится (строка 49а). - Остаточные Cyrillic-хардкоды пар-гейтед DC-чекеров (
checks/repair.go:131,checks/checkers.go:457,474) — инертны без dc-данных пары; кандидат наc.wordScript(строка 79). prompts/zh-ru/editor-mono.md— DORMANT-референс D13.1 (не боевой, тестом закреплён); боевой =prompts/zh-ru/editor.mdv3-discourse (БИЛИНГВ). Глосс-капы санитайзера (6 CJK / 24 лат-токен) — tunable-эвристики (D39.5). Флоры min_max_tokens = схема (D24.3).- Хвост LOW/NOTE-находок адверсариала D39.4 — в ledger-очереди (строка 53).