Add step-3a onboarding prompt for the fresh backend session: real chunker, txt/epub import, ruby capture
This commit is contained in:
parent
e4ca3a57fd
commit
073ed62791
1 changed files with 129 additions and 0 deletions
129
docs/BACKEND_SESSION_PROMPT_CHUNKER.md
Normal file
129
docs/BACKEND_SESSION_PROMPT_CHUNKER.md
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
# Промт сессии «Бэкенд — Шаг 3a: настоящий чанкер + импорт + ruby-захват»
|
||||
|
||||
Ты — инженер-бэкенд проекта **TextMachine** (Go, бэкенд AI-перевода крупных художественных текстов ранобэ/вебновелл, zh/ja/en→ru, мультиагентный пайплайн). Рабочий каталог `/home/ubuntu/projects/textmachine`, твоя зона — `backend/`. Это **продолжающая** сессия: Фаза 0 (каркас), Веха 1 (capability-слой + snapshotID), Веха 2 (chunk-loop + disposition/classify) и Веха 2.5 (coverage-гейт + single-hop эскалация + D12-модель отказов + F3-фикс + live-conformance) уже сделаны, приняты (селфревью + внешнее ревью) и закоммичены. Ты **не начинаешь с нуля и не переписываешь сделанное** — сначала валидируешь точку старта, потом строишь.
|
||||
|
||||
Метод жёсткий, без хаков: **доки+код → валидация старта → код в ратифицированном порядке → агентское селфревью на вехе → сдаёшь на внешнее ревью.** Никаких срезов углов, закомментированных проверок, обходов инварианта детерминизма, «потом починю». Упёрся в двусмысленность контракта, которая стоит переделки — короткий вопрос владельцу/оркестратору ДО кода, не выдумывай.
|
||||
|
||||
Идёт **параллельная** работа (память v2 / Полигон) — коммиты сыпятся в `main` часто. Твой шаг спроектирован так, чтобы почти не пересекаться с ними по файлам (см. §7). При пересечении в `runner.go` — разрулишь ребейзом; держи там след минимальным.
|
||||
|
||||
---
|
||||
|
||||
## 0. ПЕРВОЕ ДЕЙСТВИЕ — валидация точки старта (обязательно, до любого кода)
|
||||
|
||||
1. **git:** `git -C /home/ubuntu/projects/textmachine log --oneline -8`. SHA плавают (параллельные сессии ребейзят) — ориентируйся по СООБЩЕНИЯМ. Последний backend-коммит должен быть **«Close external-review findings: fold classifier version and local backend tag into the snapshot…»** (Веха 2.5 закрыта). Рабочее дерево по `backend/` должно быть чистым (грязные `eval/`, `docs/experiments/*`, `docs/research/*` — зоны Полигона/памяти, НЕ трогать/НЕ коммитить).
|
||||
2. **Сборка/тесты** (Go 1.26.4 в `~/sdk/go`; из `backend/`): `export PATH="$HOME/sdk/go/bin:$PATH"; go build ./... && go vet ./... && go test ./... -race -count=1`. Всё зелёное. Не зелёное — находка, разбирайся до кода. (cwd Bash может сбрасываться — заходи в `backend/` явно.)
|
||||
3. **Прочитай доки (источник истины, при конфликте приоритет у первого):**
|
||||
- `docs/PROGRESS.md` секция **## Бэкенд** — журнал; последние записи «Сессия 4: Веха 2» и все записи Вехи 2.5 (coverage-гейт, эскалация, D12, live-conformance, внешнее ревью). Читай дословно — там инварианты, на которые ты встаёшь.
|
||||
- `docs/architecture/05-decisions-log.md` — **D2** (диспозиция), **D9** (приёмочная книга = ja→ru ранобэ ~150k токенов; ruby первого появления → глоссарий-лок имени, НЕ ruby в выходной вёрстке; хедж «полный ruby-лок — fast-follow»), **D12** (модель отказов; **Q1: сегментация coverage-гейта Python-locked на `refusal_bench.py::split_sentences`, менять только Python-first lock-step** — это про ВЫХОДНОЙ гейт, не про твой входной чанкер, но знай границу), а также «Следствия для плана» §3 (ruby-парсер на инжест).
|
||||
- `docs/architecture/03-implementation-notes.md` — §3.1 (детерминизм/request-hash), §3.2 (snapshot), §3.4 (ключ TM = хэш нормализованного src-чанка; **перечанкование инвалидирует by design**), §3.7 (coverage-гейт), §3.8 (граница «конфиг vs код» Р2).
|
||||
- `docs/architecture/04-unhappy-paths.md` — **§4** (полнота/целостность: CJK-осколки, **потеря ruby/фуриганы на инжесте** — «извлекать чтения на инжесте, не сохранять инлайн-разметку, а выхватить ruby в глоссарий»), §1 (консистентность имён — зачем ruby-лок).
|
||||
- `docs/architecture/02-mvp-plan.md` блок «Фаза 1»: «Импорт/чанкинг: txt/epub; **epub v1 = извлечение текста глав по spine, экспорт простым xhtml** (инлайн-разметка/ruby не сохраняются); чанк **1–2k токенов по границам абзацев**; перекрытие — **read-only контекст**».
|
||||
- `docs/architecture/01-decisions.md` Р2/Р6 (не редактировать; правки только через 03-notes/PROGRESS).
|
||||
4. **Прочитай код, который расширяешь (инварианты не ломать):**
|
||||
- `backend/internal/pipeline/chunker.go` — **твоя главная цель.** Сейчас ПЛЕЙСХОЛДЕР `SplitChunks(source string) []Chunk` (главы по form-feed `\f`, паковка абзацев ≤`targetChunkChars=1500`). `Chunk{Chapter,ChunkIdx,Text}`. Ты его ЗАМЕНЯЕШЬ.
|
||||
- `backend/internal/pipeline/render.go` — `chunkerVersion = "chunker-v2-ff-para-pack"` (бампаешь), `EstimateTokens` (детерминированная токен-оценка — используй для «~1–2k токенов»), `NormalizeSource`, `msgsContentHash`, `RequestHash`.
|
||||
- `backend/internal/pipeline/runner.go` — `TranslateBook` (грузит источник: `os.ReadFile`→`NormalizeSource`→`SplitChunks`; ты вклиниваешь сюда `Ingest`), `translateChunk` (цикл стадий — **НЕ меняешь семантику**), `runStage`/`runAttempt` (эскалация/coverage внутри — **не трогаешь**), `snapshotID()` (куда фолдится `chunkerVersion`).
|
||||
- `backend/internal/store/migrate.go` — миграции v1→v3 (v3 = `escalation`-колонка). Твоя миграция — **v4** (append-only, IF NOT EXISTS, tx на шаг, **никогда не редактируй прошлый шаг**).
|
||||
- `backend/internal/config/book.go` — `Book.SourceFile` (может стать `.epub`), `LoadBook`.
|
||||
- `backend/internal/pipeline/coverage.go` — coverage-гейт (читать, НЕ трогать): важно для §7, он берёт `ch.Text` как source и скипает чанки `< MinChunkChars(500)`.
|
||||
5. **Прогони** `tmctl report --config example/book.yaml` (из `backend/`) — $0, LLM не зовётся. (Если `example/*.db` протух после смены схемы — удали `example/*.db*`, он gitignored, пересоздастся.)
|
||||
|
||||
**Инвариант, который держишь свято:** любой инпут, меняющий wire-запрос ИЛИ `max_tokens`, обязан входить либо в snapshot (`snapshotID`/`stageSnap`), либо в `RequestHash`. Плюс `msgsContentHash` в `chunk_status` (исходник НЕ в snapshot → правка src инвалидирует позиционный resume). Смена сегментации чанкера = **осознанная инвалидация** (`chunkerVersion` в snapshot → громкий `--resnapshot`, не тихий промах §3.4/R6/D5.2). Ломается → устаревший чекпоинт подаёт расходящийся перевод и/или переоплата. Не откати.
|
||||
|
||||
---
|
||||
|
||||
## 1. Состояние на входе (что готово — НЕ пересоздавать)
|
||||
|
||||
- **Chunk-loop + disposition (Веха 2):** `TranslateBook → translateChunk → runStage → runAttempt`, ось attempt, `chunk_status` (резолв поверх чекпоинтов, `content_hash`-guard на resume), exit-коды 0/2/1, три анти-веджа. **Плейсхолдер-чанкер** `SplitChunks` — ровно то, что ты заменяешь; шов под замену я делал специально.
|
||||
- **classify (Веха 2):** эхо (`cjk_artifact`, cjkShare>0.15), пусто, refusal-blacklist, length/loop — детектятся. Не трогаешь.
|
||||
- **Веха 2.5 (закрыта):** coverage-гейт `excision_suspect` (порт `refusal_bench` 1:1, в `coverage.go`, боевой `enabled:false`); single-hop эскалация (в `runStage`, отдельная ось модели в hash, боевой `budget_usd:0`); D12-модель отказов; F3 idempotency (at-most-once); live-conformance (`//go:build live`); DeepSeek эхо-мина закрыта fail-fast-гейтом. **Гейты и эскалация дремлют по дефолту** — тебе не мешают.
|
||||
- `context.overlap_tokens:200`, `stm_depth:2` — **в конфиге есть, но в `msgs` НЕ инъектятся** (это память v2 / шаг 4).
|
||||
|
||||
**Чего НЕТ (и что из этого твоё):**
|
||||
- Настоящий чанкер — **твоё** (сейчас плейсхолдер).
|
||||
- Импорт txt/epub — **твоё** (сейчас `os.ReadFile` одного txt).
|
||||
- ruby-захват на инжесте — **твоё** (сейчас ruby-разметка epub убила бы текст).
|
||||
- Overlap-инъекция и ruby→глоссарий-лок — **НЕ твоё** (отложено, см. §3).
|
||||
|
||||
---
|
||||
|
||||
## 2. МИССИЯ — Шаг 3a: настоящий чанкер + импорт (txt/epub) + ruby-захват
|
||||
|
||||
Снять плейсхолдер-чанкер, дать реальную сегментацию и импорт, чтобы книга (D9: ja→ru ранобэ epub) прогонялась end-to-end на настоящих границах. Три части:
|
||||
|
||||
### A. Настоящий сорс-чанкер (замена `SplitChunks`)
|
||||
- Сегментация ИСХОДНИКА по **границам предложений и абзацев**, паковка в чанки **~1–2k токенов** (используй `EstimateTokens`; целевой размер — const, покрытый `chunkerVersion`, ИЛИ конфиг — см. §7d). Не резать предложение; предпочитать границы абзацев; абзац/предложение больше цели = свой чанк.
|
||||
- Предложения: терминаторы CJK+латиница (`。!?!?…` и т.п.), схлопывание последовательностей, детерминированно. **Это ОТДЕЛЬНАЯ логика от coverage-гейтова `split_sentences`** (тот — по РУССКОМУ ВЫХОДУ, Python-locked; твой — по ИСХОДНИКУ, `chunkerVersion`-versioned, Р2 = код). Не путать, не связывать.
|
||||
- **Держи контракт `Chunk{Chapter,ChunkIdx,Text}`** — цикл раннера зависит от него (§4). `ChunkIdx` сбрасывается на 0 в каждой главе.
|
||||
- **Бампни `chunkerVersion`** (напр. `chunker-v3-sentence-pack`). Чистая функция (детерминизм §3.1): без map-итераций в порядке вывода, без time/rand. Тест `chunker_test.go` (7 тестов) — твоя страховка на время рефактора; обнови под новую семантику.
|
||||
|
||||
### B. Импорт txt/epub — новый ingest-слой
|
||||
- `Ingest(path) (*Document, error)`, где `Document = {Chapters []Chapter, Ruby []RubyReading}`; диспетчер по расширению.
|
||||
- **txt:** читать + `NormalizeSource`; главы — реши convention (сохранить `\f` как раздел глав ради обратной совместимости example, ИЛИ heading-эвристика; согласуй если сомнение). Ruby в txt нет.
|
||||
- **epub:** ЧИСТО stdlib (проект CGO-free, минимум зависимостей — `modernc/sqlite`, `yaml.v3`, `golang.org/x/text`; **новых внешних зависимостей не тащить**). `archive/zip` → `META-INF/container.xml` → путь OPF → OPF `spine` (порядок `itemref`) + `manifest` (`id`→`href`) → читать каждый spine-xhtml по порядку (= глава) → `encoding/xml` (`d.Strict=false`, `d.Entity=xml.HTMLEntity`, `d.AutoClose=xml.HTMLAutoClose`) → извлечь текст, **стрипнуть теги**, **выхватить `<ruby>base<rt>reading</rt></ruby>`** (в тело — base, чтения — в §C). epub v1 = текст по spine (04-mvp); инлайн-разметку/вёрстку не сохраняем (осознанное ограничение v1).
|
||||
- `TranslateBook` зовёт `Ingest` вместо `ReadFile+NormalizeSource`, потом `SplitChunks(doc.Chapters)`. Минимальный след в `runner.go` (замена 3 строк загрузки источника + персист ruby).
|
||||
|
||||
### C. Ruby-захват + персист (для потребления памятью v2)
|
||||
- Захватывать `(base, reading, first_chapter)` при парсе epub. **НЕ инъектить в промпт** (это память v2 + детерминизм-load-bearing — см. §7d).
|
||||
- Персист в **новую v4-таблицу** `ruby_readings(book_id, base, reading, first_chapter, …)` (PK напр. `(book_id, base, reading)`, `first_chapter`=MIN), идемпотентный upsert. Память v2 (шаг 4) это потребит → глоссарий-лок имён (D9). Форму таблицы согласуй, если сомнение (это контракт под шаг 4).
|
||||
- **Куда лок НЕ делаем сейчас:** запись в глоссарий/лок рендеринга — шаг 4 (глоссарий-схемы ещё нет). D9 явно допускает ruby-лок как fast-follow.
|
||||
|
||||
---
|
||||
|
||||
## 3. Что НЕ делать в Шаге 3a (отложено — согласовано с владельцем)
|
||||
|
||||
- **Overlap-инъекция (read-only контекст соседних чанков).** `RenderVars` — ФИКСИРОВАННАЯ структура `{Book,Text,Draft}`, `Render` падает на неизвестном `{{…}}`. Overlap нельзя запечь в `Chunk.Text` — он потечёт в `EstimateTokens`/`maxTokens`, `msgsContentHash`, `RequestHash` И в **source coverage-гейта** (overlap в `Text` схлопнет `len_ratio` → ложный `excision_suspect`). Настоящий overlap = новое поле `RenderVars`/`Chunk` + плейсхолдер шаблона + правка `Messages()`/`runStage` — это **та же msgs-инъекционная поверхность, которую строит память v2** (Р5-раскладка: system → [глоссарий/STM/overlap] → user). Строим её ОДИН раз (шаг 4 / общий context-assembly), не дважды.
|
||||
- **Ruby→глоссарий-лок** — нужен глоссарий (память v2). Сейчас только ЗАХВАТ+персист (§C).
|
||||
|
||||
Если по ходу окажется, что overlap критичен для приёмки раньше памяти — **короткий вопрос владельцу**, не тащи его в 3a молча.
|
||||
|
||||
---
|
||||
|
||||
## 4. Шов, который сохраняешь (контракт цикла)
|
||||
|
||||
- `TranslateBook`: `Ingest` → `chunks := SplitChunks(doc.Chapters)` → `for _, ch := range chunks { translateChunk(ctx, snapID, ch) }`. Ошибка если чанков 0.
|
||||
- `translateChunk(ctx, snapID, ch Chunk)` сеет `out.Chapter/ChunkIdx` из `ch`, крутит стадии; **`ch.Text` идёт в ТРИ места** — не сломай ни одно:
|
||||
1. `Messages(..., RenderVars{Book, Text: ch.Text, Draft: prev})` → `{{text}}` на проводе.
|
||||
2. `classifyOutput(role, ch.Text, output, finish)` → **`ch.Text` = source coverage-гейта** (потому overlap НЕ в `Text`).
|
||||
3. `RequestHash(bookID, ch.Chapter, ch.ChunkIdx, attempt, …)` → ключ чекпоинта.
|
||||
- `prev` = вывод ПРЕДЫДУЩЕЙ СТАДИИ (draft→edit), НЕ соседнего чанка. Первая стадия — `prev=""`.
|
||||
- Новые границы → новые `RequestHash`/эскалационный `fbHash`: **безопасно, если бампнул `chunkerVersion`** (`--resnapshot`-гейт пере-пинит, старые чекпоинты/эскалации не false-хитят). `EscalationSpentUSD` суммирует per-book, границе-независимо. Спец-обработки не надо сверх бампа версии.
|
||||
|
||||
---
|
||||
|
||||
## 5. Инварианты (жёстко)
|
||||
|
||||
- **Детерминизм священен.** Чанкер — чистая функция; `chunkerVersion` бампнут; целевой размер/параметры — либо const (покрыт версией), либо конфиг → **фолдить в snapshot** как `coverageSnap` фолдит пороги гейта (не полагаться только на строку версии — находка внешнего ревью Вехи 2.5). Ruby, ЕСЛИ бы инъектился в промпт (не в 3a), был бы детерминизм-load-bearing.
|
||||
- **Деньги.** 3a не делает боевых LLM-вызовов (импорт/чанкинг — оффлайн; тестируй на моках + `tmctl report` $0). Не сломай reserve/settle/эскалацию/F3.
|
||||
- **Границы Р2/§3.4.** Чанкер — код (правила сегментации), версионируется `chunkerVersion`. Перечанкование инвалидирует TM by design (это плата за корректность, не баг).
|
||||
- **disposition-контракт Вехи 2 и escalation/coverage Вехи 2.5 не ломать** (`jobs.status`=инфра, gate off-by-default, эскалация off-by-default).
|
||||
- **D12 caution:** «наивный параллелизм чанков» НЕ строить (гонка `Runner.clients` + «коммит терминов только на границе джоб»). Цикл остаётся последовательным.
|
||||
- **Без хаков, без новых внешних зависимостей** (epub — stdlib zip+xml).
|
||||
|
||||
## 6. Правила проекта
|
||||
|
||||
- Метод: доки+код → валидация → код → **агентское селфревью на вехе** (адверсариальный многоагентный Workflow: дименсии → находки → независимая верификация CONFIRMED/REFUTED с конкретным сценарием инпут→неверный-выход → исправь подтверждённые + регресс-тесты, **мутационно-проверенные**: сломай — тест должен упасть) → сдаёшь оркестратору/владельцу.
|
||||
- Дименсии селфревью Шага 3a (минимум): **детерминизм** (чанкер чист, `chunkerVersion`/параметры покрыты snapshot, перечанкование = громкий resnapshot); **сегментация** (границы предложений/абзацев корректны на zh/ja/en, чанки в коридоре ~1–2k, не режет предложение, не путается с coverage-`split_sentences`); **epub-парсинг** (spine-порядок, стрип тегов, entity, битый/пустой epub — fail-loud не паника; ruby-извлечение корректно, base в тело); **интеграция** (coverage-source = чистый `ch.Text` без ruby-мусора и без overlap; `ch.Text` в 3 местах цел; чанки `<500` симв. молча выключают coverage — задокументируй/флагни); **регрессии** (Веха 0/1/2/2.5 не сломаны; e2e мульти-глава epub→чанки→мок-перевод; resume $0; determinism-тест перечанкования).
|
||||
- **Не редактировать** `docs/research/*`, `01/02-decisions` — правки только через `03-implementation-notes.md` и `PROGRESS.md` (## Бэкенд). **Не трогать/не коммитить** `eval/`, `docs/experiments/*`, `docs/research/*` (зоны Полигона/памяти; читать можно). **Не коммитить** `settings.local.json`, `*.db`.
|
||||
- Коммиты: одно предложение на английском ≤30 слов, без трейлера `Co-Authored-By`, без многострочного тела. На `main` прямо (паттерн проекта), только `backend/` + `PROGRESS`, никогда `eval/`.
|
||||
- PUML не рендерить в картинки (у владельца PlantUML-расширение VS Code).
|
||||
- Автопамять проекта (`MEMORY.md` + provider-wiring-gotchas) — сверься, обнови по находкам.
|
||||
|
||||
## 7. Риски интеграции (проверено картой кода — читай внимательно)
|
||||
|
||||
- **(a) `RenderVars` — фикс-структура `{Book,Text,Draft}`**, `Render` падает на неизвестном `{{…}}`. Любая инъекция (overlap/ruby/память) требует нового поля + плейсхолдера + правки `Messages()` — это НЕ чистый seam-swap. В 3a этого не делаешь (§3); значит твой чанкер отдаёт только `Chunk.Text`, и ничего кроме сегментации в msgs не меняется.
|
||||
- **(b) coverage-гейт берёт `ch.Text` как source и скипает чанки `< MinChunkChars(500)`** (коридоры калиброваны на 500–2500 симв.). Настоящий чанкер, дробящий на суб-500 чанки, **молча выключит excision-гейт** для них, а малый `nSrc` шумит `sent_cov`. Целься в ~1–2k токенов (не в мелочь); задокументируй границу. И держи overlap/ruby ВНЕ `ch.Text`.
|
||||
- **(c) новые границы vs эскалация/resume** — безопасно при бампе `chunkerVersion` (§4). `EscalationSpentUSD` границе-независим.
|
||||
- **(d) детерминизм-хуки:** `chunkerVersion` — ЕДИНСТВЕННОЕ snapshot-поле сегментации; отдельных полей под параметры чанкера НЕТ. Любая смена поведения (размер, ruby-правила, паковка) ОБЯЗАНА бампнуть версию, иначе тихий расходящийся ре-пэй. Если размер станет конфиг-настраиваемым — **фолдни в top-level snapshot** (как `coverageSnap`), не полагайся на строку версии. `msgsContentHash` поймает смену контента на resume, но только бамп `chunkerVersion` делает это ГРОМКИМ `--resnapshot`, а не тихим per-chunk ре-переводом.
|
||||
|
||||
## 8. Сборка/прогон + открытые вопросы
|
||||
|
||||
Из `backend/`: `export PATH="$HOME/sdk/go/bin:$PATH"; go build ./... / go vet ./... / go test ./... -race -count=1` — зелёные на каждом шаге. Тесты: юниты чанкера (сегментация/границы/детерминизм), epub-парсинг (собери синтетический epub-zip в памяти теста, `archive/zip`), ruby-извлечение, мульти-глава e2e на мок-провайдере, determinism (перечанкование = resnapshot), идемпотентный persist ruby. Файлы БД только на ext4 (не /mnt/c).
|
||||
|
||||
**Открытые (уточни ДО кода, если стоят переделки):**
|
||||
- Разделитель глав для **txt** (сохранить `\f` ради обратной совместимости example vs heading-эвристика).
|
||||
- Форма таблицы `ruby_readings` (это контракт под шаг 4 — согласуй с тем, как память v2 будет её читать; при сомнении спроси).
|
||||
- Целевой размер чанка **const (покрыт `chunkerVersion`) vs конфиг (фолдить в snapshot)** — реши по §7d; если делаешь конфиг, обязательно snapshot-fold.
|
||||
- Порядок vs память v2 (шаг 4): 3a изолирован, но overlap/ruby-лок ждут инъекционной поверхности памяти — не тащи их в 3a без отмашки.
|
||||
|
||||
Welcome to the backend. Валидируй старт, замени чанкер, дай импорт, захвати ruby — и держи msgs-поверхность нетронутой для памяти v2.
|
||||
Loading…
Add table
Reference in a new issue