textmachine/backend
2026-07-11 14:48:03 +03:00
..
cmd/tmctl Land backend package 5: per-model max_tokens floor on primary and hop, postcheck base-dst union, budget-refusal and coverage-recall pins, D23.4 tails; golden byte-identical 2026-07-11 02:13:21 +03:00
configs Land backend D15.2 stage A: bilingual editor with source injection, reflow, output-sanitizer gate, golden re-capture with floor/union/sanitizer pins, spec v3.1 design 2026-07-11 14:48:03 +03:00
docs Land backend D15.2 stage A: bilingual editor with source injection, reflow, output-sanitizer gate, golden re-capture with floor/union/sanitizer pins, spec v3.1 design 2026-07-11 14:48:03 +03:00
example Add Phase 0 backend skeleton: LLM core with native Anthropic adapter, money ledger, SQLite store with atomic chunk checkpoints, deterministic C1 mini-runner, tmctl CLI, configs and prompts 2026-07-04 09:04:06 +03:00
internal Land backend D15.2 stage A: bilingual editor with source injection, reflow, output-sanitizer gate, golden re-capture with floor/union/sanitizer pins, spec v3.1 design 2026-07-11 14:48:03 +03:00
prompts Land backend D15.2 stage A: bilingual editor with source injection, reflow, output-sanitizer gate, golden re-capture with floor/union/sanitizer pins, spec v3.1 design 2026-07-11 14:48:03 +03:00
.env.example Drop Anthropic from the model stack per owner decision: remove provider and Claude models, repoint escalation and C2 judge to non-Anthropic 2026-07-04 16:35:52 +03:00
go.mod Harden Phase 0 after self-review: snapshot coverage, response-price fallback, usage clamp, job-status lifecycle, config guards, source normalization, hash robustness 2026-07-04 14:49:43 +03:00
go.sum Harden Phase 0 after self-review: snapshot coverage, response-price fallback, usage clamp, job-status lifecycle, config guards, source normalization, hash robustness 2026-07-04 14:49:43 +03:00
README.md Land backend package 5: per-model max_tokens floor on primary and hop, postcheck base-dst union, budget-refusal and coverage-recall pins, D23.4 tails; golden byte-identical 2026-07-11 02:13:21 +03:00

backend/ — Go-бэкенд TextMachine

Зона сессии «Бэкенд». Контракт — docs/architecture/05-decisions-log.md (D1D23); контракты Фазы 0 — 03-implementation-notes.md; квирки провайдеров — docs/experiments/00-provider-quirks.md. .env не читать.

Карта пакетов

Пакет Что делает Ключевые файлы
cmd/tmctl CLI: translate / report / status (read-only проекция N/M+паспорта глав+деньги, --json; работает ПРИ живом прогоне — store без flock) / redrive (переатака флагнутых: --chapter/--chunk/--reason/--dry-run, D15.3). main — тонкая обвязка: разбор аргументов/exit-коды/.env/рендеры вынесены в тестируемые функции (пакет №4) main.go, invocation.go, render.go, dotenv.go, internal/pipeline/status.go
internal/llm OpenAI-совместимый транспорт + retry/backoff, capability-слой (budget_field/temperature/reasoning per-модель), failover (написан, НЕ подключён — D4: только local-роли), нативный Anthropic = DEPRECATED-референс под будущий Gemini httpllm.go, capability.go, failover.go
internal/ledger Цены по usage (вкл. reasoning/cache-поля), PriceForResponse по фактической модели pricing.go
internal/store SQLite (modernc, CGO-free), идемпотентные миграции v1v7, reserve/settle+checkpoint, chunk_status, глоссарий, ruby, retrieval_state (вкл. стиль-флаги v7), request_log; OpenReadOnly — без flock/миграций/recovery для status/report ledger.go, migrate.go, glossary.go, store.go
internal/config fail-fast загрузка models/pipeline/book; эхо-мина-гейт echoMineViolation; CheckRunnable блокирует неисполнимое (C2/fanout/judge) models.go, pipeline.go
internal/pipeline Раннер, декомпозированный по связным единицам (пакет №4): сетап runner.go → снапшот snapshot.go → сид seeding.go → цикл книги bookrun.go → петля чанка chunkrun.go → стадия stagerun.go → эскалация с ре-гейтом escalation.go → resume resume.go; чанкер v4, ingest txt/epub+ruby, classify/coverage, банк памяти v2 (Aho-Corasick, спойлер-окна, disposition, post-check), рендер+инъекция. Ф2-стройки добавляются файлом: канал B → escalation.go, annotator/voice-инъекция → chunkrun.go bookrun.go, stagerun.go, memory.go, disposition.go, coverage.go
internal/obs trace_id, структурные логи (contextHandler несёт book/chapter/chunk/stage/role из ctx), safego

Инварианты — ЛОМАТЬ НЕЛЬЗЯ (каждый закреплён тестами)

  1. Деньги: reserve → call → settle+checkpoint одной транзакцией; committed == SUM(checkpoints) переживает kill -9 (kill9_test.go); цена — по фактически ответившей модели; потолки книга/день считают committed+reserved. Одно исключение — явный tmctl redrive (D15.3): удаляет чекпоинты сброшенных стадий, НЕ возвращая committed (реально потраченные деньги остаются) → после redrive committed ≥ SUM(checkpoints) — безопасное направление (потолок не занижает), задокументировано в ResetChunkStages.
  2. Snapshot-дисциплина: всё, что влияет на wire-байты ИЛИ на вердикты (capability, память content-hash, context-assembly, версии classify/coverage/chunker/maxtok, эскалационные оверрайды), свёрнуто в snapshotID; правка = громкий --resnapshot. ⚠ Любой resnapshot = переоплата книги (D15) — content-addressed reuse спроектировать до онгоинга.
  3. Эхо-мина DeepSeek: thinking у deepseek НИКОГДА не отключать — reasoning:"off" там осознанный no-op; echoes_when_thinking_off + рекурсивный скан extra_body валят конфиг fail-fast. У grok наоборот: off = ЯВНЫЙ reasoning_effort:"none".
  4. Порядок classify: refusal-blacklist / CJK-echo — ДО length/empty (усечённый отказ не превращается в платный ретрай); retry-flagged только {length, empty} на той же модели; loop-чек ПЕРЕД удвоением max_tokens.
  5. Эскалация: ровно 1 хоп на другую модель, результат РЕ-гейтится, retry-бюджет не сбрасывается, editor pinned (escalate_to только на translator — принуждается валидацией), канал adult — только permissive-провайдеры (fail-closed).
  6. Детерминизм: рендер — чистая функция snapshot; сортировки перед записью; никакого map-order в выводе; length-prefixed хэши. Сорс-чанкер lossless (fuzz-тесты).
  7. Нейтральность адаптера: internal/llm не знает про перевод (src/target/coverage) — контент-вердикты живут в раннере (disposition-слой, post-settle).

Подготовка ja-книги (чек-лист сида)

  • Kana-написания имён = matchable. Ruby-чтения (<ruby>鈴木<rt>すずき</rt></ruby>) захватываются на инжесте и авто-подцепляются как alias соответствующей ручной записи (D16.4, attachRubyAliasesToManual), поэтому кана-форма имени, у которого есть кандзи+фуригана, матчится автоматически. Сверх этого — руками: имя, которое встречается ТОЛЬКО каной (без кандзи-написания с ruby), не даст авто-alias → внеси кана-написание в сид как aliases: вручную, иначе кана-упоминание молча не заматчится («тихо пусто»).
  • Двойные чтения (強敵→とも) — под флагом. Авто-alias ставится для формы «all-Han база + all-kana чтение»; семантическое двойное чтение неотличимо оффлайн (нужен yomi-словарь, B6). Такой alias фаерится AMBIGUOUS (короткий/коллизионный) и проходит post-check, но длинное двойное чтение может дать CONFIRMED-ложь — при подозрении убери его из сида. Полная дизамбигуация — Фаза 2 (B6/Палладий-Поливанов).
  • Kana-precision ≥4 не замерена (minKeyLenPhonetic=3 — консервативный дефолт): кана-ключи НЕ проверяются на границу слова (в отличие от латиницы/кириллицы — D16.3), т.к. у каны нет сегментации (как у Han) — иначе ломается частый кейс «имя+частица» (すずきは). ≥4-кана-компаунд-precision — задача полигона (расширение E1) + токенизатор B6.

Как гонять

go build ./... && go vet ./... && go test ./... -race   # всё зелёное = норма
go run ./cmd/tmctl translate --config example/book.yaml # реальные вызовы — нужны ключи в .env
go run ./cmd/tmctl report --config example/book.yaml    # $0, читает store
# live-conformance (реальные провайдеры, платно, вне CI):
set -a; . ./.env; set +a; TM_LIVE=1 go test -tags live -run TestLive -v ./internal/pipeline/

Golden-гард детерминизма (internal/pipeline/golden_test.go + testdata/golden/): на статичной фикстурной книге пинит бит-в-бит snapshotID, request_hash всех вызовов (вкл. эскалацию), wire-тела, вердикты и resume-байты. Красный golden = рефакторинг изменил wire/вердикты = --resnapshot = переоплата книги (D15). Обновлять ТОЛЬКО на осознанной, ратифицированной смене поведения: TM_UPDATE_GOLDEN=1 go test ./internal/pipeline/ -run TestGolden.

Финал каждой вехи — агентское адверсариальное селфревью (несколько дименсий → независимая верификация каждой находки → фиксы mutation-verified: реверт фикса валит именно его тест). Внешнее ревью — оркестратор.

Известный техдолг (не трогать молча — см. D-лог)

F3 at-most-once (после D15.2); Escal.Chains — валидируемый мёртвый конфиг (раннер читает только escalate_to, полные цепочки = шаг 7); D3-цепочка/канал B ЗАВЕДЕНЫ в models.yaml (пакет №3, слаги live-фактчекнуты 2026-07-10 — gemini ИМЕННО -preview); escalation.budget_usd>0 требуется, чтобы single-hop escalate_to черновика стрелял (приёмка); per-model min-budget теперь СХЕМА (capabilities.min_max_tokens: deepseek 8000 / gemini 8000 / kimi 16000 — D24.3, флор по МОДЕЛИ вызова, у хопа своя; glm/grok без флора), не комментарий; реализация content-addressed resume (D15.2) — после обязательных v3.1-правок (D22.2: langs→verdictSnapshotID, развязка cache_ttl).