Reissue the book-writer prompt after a second refuter: a copied book still migrates the original through the symlink, java exists so epubcheck runs, and two per-unit holes were missing
This commit is contained in:
parent
8977001018
commit
31e889d446
3 changed files with 282 additions and 84 deletions
|
|
@ -4,13 +4,15 @@
|
|||
|
||||
Проект переводит книги и **не умеет отдать книгу**. Ни одной стороной. Движок отдаёт только поток:
|
||||
`renderExport` (`backend/cmd/tmctl/render.go:565`) печатает стабильный JSON `tm-export-v1` либо
|
||||
`--plaintext` с аудит-баннерами `=== CHAPTER N CHUNK M ===`. Греп `epub|fb2|docx` по `backend/**` минус тесты даёт **43** попадания, из них 29 в `internal/chunk/`
|
||||
`ingest.go`. ⚠ **И писатель EPUB в дереве УЖЕ ЕСТЬ** — `backend/internal/chunk/chunktest/epub.go:37-112`
|
||||
`--plaintext` с аудит-баннерами `=== CHAPTER N CHUNK M ===`. Греп `epub|fb2|docx` по `backend/**/*.go` минус
|
||||
`_test.go` даёт **43** строки, из них 26 в `internal/chunk/ingest.go` — всё на стороне ЧТЕНИЯ.
|
||||
⚠ **И писатель EPUB в дереве УЖЕ ЕСТЬ** — `backend/internal/chunk/chunktest/epub.go:37-112`
|
||||
(`BuildEPUB`/`BuildEPUBAt`): настоящий OCF-контейнер, `mimetype` первым и `zip.Store`, `container.xml`,
|
||||
`content.opf` с manifest+spine, XHTML. Он ТЕСТОВЫЙ, но обратим ингесту по построению и запинен
|
||||
(`ingest_test.go:537` `TestEPUBFixtureMimetypeIsOCFConformant`). Его собственная шапка предупреждает:
|
||||
дублирование zip/OPF-строителя развело бы две копии. **Начинать с него, а не с нуля.**
|
||||
Платформа обещанные каноном ручки выдачи не монтирует, и её `export_formats` захардкожен пустым.
|
||||
`content.opf` с manifest+spine, XHTML. Он ТЕСТОВЫЙ, обратим ингесту по построению и запинен
|
||||
(`ingest_test.go:537` `TestEPUBFixtureMimetypeIsOCFConformant`). Его шапка предупреждает: дублирование
|
||||
zip/OPF-строителя развело бы две копии. **Начинать с него — как именно, размечено в §3.1.**
|
||||
Платформа обещанные каноном ручки выдачи не монтирует, её `ExportFormats` захардкожен пустым
|
||||
(`platform/cmd/tmplatformd/runner.go:191`).
|
||||
|
||||
**Что решит твой результат:** переведённую книгу можно будет открыть в читалке. Сегодня цель №1 канона
|
||||
(«издательское художественное качество») не проверяется ничем на выходе: результат некуда открыть.
|
||||
|
|
@ -22,47 +24,113 @@
|
|||
## 1. Зона записи и git
|
||||
|
||||
Пишешь только в `backend/`. **Не коммитишь**: дерево готовишь и передаёшь оркестратору. Канон git — в
|
||||
`CLAUDE.md`, здесь не дублируется. Чужие незакоммиченные файлы не трогать.
|
||||
`CLAUDE.md`, здесь не дублируется.
|
||||
|
||||
## 2. Карта чтения — ПЯТЬ позиций, дальше только по их ссылкам
|
||||
|
||||
1. `backend/internal/pipeline/export.go` — **прежде всего блок CAVEATS над `Export`**: там названы три
|
||||
состояния, которые твой файл обязан не соврать (дрейф конфига, ghost-строки, pending-юниты).
|
||||
2. `backend/cmd/tmctl/render.go:562-630` — существующий рендер и то, чем `--plaintext` НЕ годится
|
||||
в качестве книги (аудит-баннеры, флаги в скобках).
|
||||
1. `backend/internal/pipeline/export.go` — **прежде всего блок CAVEATS над `Export`**: три состояния
|
||||
КНИГИ, о которых файл обязан не соврать. ⚠ Ещё два состояния — ПОЮНИТНЫЕ, в CAVEATS их нет; они в §3.2.
|
||||
2. `backend/cmd/tmctl/render.go:562-630` — существующий рендер: чем `--plaintext` НЕ годится в качестве
|
||||
книги (аудит-баннеры, флаги в скобках) и арифметика `withheld`/`incomplete` (`:573-587`), которую
|
||||
твой файл обязан повторить.
|
||||
3. `backend/internal/chunk/ingest.go` — как книга РАЗБИРАЕТСЯ из epub: писатель обязан быть обратим к
|
||||
этому чтению, иначе круг «загрузил → получил» не замыкается.
|
||||
этому чтению, иначе круг «загрузил → получил» не замыкается (§4 — равенством, не парсом).
|
||||
4. `docs/architecture/12-go-style-notes.md` §0 — норматив общности: **пары языков это ДАННЫЕ**.
|
||||
5. `docs/PROGRESS.md` строка **201** (синтетический заголовок едет внутри текста юнита) — твоя развилка,
|
||||
см. §3.3.
|
||||
5. `docs/PROGRESS.md`, строка БЭКЛОГА **201** (`grep -n '^| 201 |'`) — синтетический заголовок едет
|
||||
внутри текста юнита; твоя граница, см. §3.3.
|
||||
|
||||
## 3. Работа
|
||||
|
||||
### 3.1 Что построить (делай РОВНО так)
|
||||
|
||||
**Движковый писатель книги из `BookExport`**, вызываемый из `tmctl`. Минимум два формата: **EPUB**
|
||||
(книга для читалки) и **чистый текст** — без аудит-баннеров, без флагов в скобках, без служебных
|
||||
маркеров. Существующий `--plaintext` не трогать и не переименовывать. ⚠ Обоснование именно такое, а не обратное:
|
||||
`eval/README.md:80` говорит «`--plaintext` для глаз, дефолтный JSON для скриптов» — то есть машинная
|
||||
поверхность это JSON, и у него ДВА потребителя: полигон и **платформа** (`platform/internal/runner/`
|
||||
`engine.go:137` → `ingest/export.go` `DecodeExport`; `STACK_DECISIONS.md:376` называет его «единственным
|
||||
каналом, несущим ТЕКСТ пары»). Менять форму `tm-export-v1` — значит менять платформу из чужой зоны.
|
||||
(книга для читалки) и **чистый текст** — без аудит-баннеров `=== CHAPTER …`, без флагов в скобках и без
|
||||
операторских маркеров вроде `gapMarker` (`render.go:56-67` — английская лексика аудит-поверхности,
|
||||
читателю не годится). ⚠ Это запрет на ОПЕРАТОРСКИЙ словарь, не на честность: как показать читателю
|
||||
неполноту — решение §3.2. Существующий `--plaintext` не трогать и не переименовывать — это его
|
||||
ВЫВОД, запинённый `export_cli_test.go`/`exportgap_cli_test.go`; вынос арифметики withheld/incomplete в
|
||||
общий хелпер допустим. JSON — машинная поверхность двух потребителей: полигон (`eval/README.md:80` —
|
||||
«`--plaintext` для глаз, дефолтный JSON для скриптов») и платформа (`platform/internal/runner/engine.go:148`
|
||||
`ExportArgs` → `:191` `ingest.DecodeExport`); менять форму `tm-export-v1` — значит менять платформу из
|
||||
чужой зоны.
|
||||
|
||||
⛔ **ОДИН файл на книгу на формат** — так требует контракт (`docs/architecture/14-api-contract/openapi.yaml:2489-2492`:
|
||||
`Export` = «A built copy of the book, behind a link»); деление на главы живёт ВНУТРИ файла (spine EPUB,
|
||||
разделители в txt), а не набором файлов.
|
||||
|
||||
**Основа — `chunktest/epub.go` (инварианты — РОВНО так, форму выноса решаешь сам).** Напрямую из
|
||||
`tmctl` он невызываем: принимает `*testing.T` и зовёт `t.Fatal`/`t.TempDir()` (`epub.go:37-50`), а
|
||||
копировать его второй раз запрещает его же шапка. Значит общую OCF-часть (`mimetype` первым и
|
||||
`zip.Store`, `container.xml`, запись entry) выносишь в прод-пакет без `import "testing"` и переводишь
|
||||
`chunktest` на неё — либо пишешь прод-писатель от его инвариантов и аргументируешь, почему две копии не
|
||||
разойдутся. Инварианты: `import "testing"` в прод-коде запрещён · тест-файлы, импортирующие `chunktest`
|
||||
(их 9), остаются зелёными БЕЗ правки ассертов и сигнатур · в прод НЕ переезжает ничего тестового:
|
||||
ручки `MType`/`EntryName`, OPF-заглушка с `<dc:title>Test</dc:title>` и висячим `unique-identifier="uid"`
|
||||
(`epub.go:89-90`), XHTML-шаблон `epub.go:103` с `<title>c</title><style>.x{color:red}</style>` — это
|
||||
ДЕКОЙ, на который сидит `ingest_test.go:95`; поднятый в прод, он уедет читателю в каждую главу.
|
||||
|
||||
⛔ **Файл обязан быть ДЕТЕРМИНИРОВАН:** два прогона на одних данных дают побайтово одинаковый файл.
|
||||
EPUB — это zip, и в нём по умолчанию лежат時metки и порядок записей; если они текут, детерминизм
|
||||
проекта (цель №6) кончается на последнем шаге. Докажи прогоном, а не рассуждением.
|
||||
EPUB — это zip. У `archive/zip` из stdlib таймметки записей по умолчанию нулевые, порядок записей =
|
||||
порядок вызовов `Create` — стартовый `chunktest/epub.go` этим уже детерминирован. Утечку вносишь только
|
||||
сам: `FileHeader.Modified`/`zip.FileInfoHeader` от файловой системы, обход map при записи, и — главное —
|
||||
`<meta property="dcterms:modified">`, которую EPUB 3 ТРЕБУЕТ (§3.3-бис): значение бери детерминированное
|
||||
из данных прогона, не `time.Now()`. ⛔ Провалившийся `cmp` чинить удалением этого элемента НЕЛЬЗЯ —
|
||||
получишь неконформный EPUB. Докажи прогоном двух ПРОЦЕССОВ, а не рассуждением.
|
||||
|
||||
### 3.2 Три состояния, о которых файл не имеет права соврать (делай РОВНО так)
|
||||
### 3.2 Пять состояний, о которых файл не имеет права соврать (делай РОВНО так)
|
||||
|
||||
`Export` их уже вычисляет — не изобретай заново, но и не игнорируй:
|
||||
- **`PendingUnits > 0`** — книга переведена НЕ ЦЕЛИКОМ. Файл, молча притворяющийся полным, — худший
|
||||
исход этого пака.
|
||||
- **`GhostRows > 0`** — есть строки вне текущего манифеста.
|
||||
Три — счётчики `BookExport`; `Export` их уже вычисляет (блок CAVEATS) — не изобретай заново:
|
||||
- **`PendingUnits > 0`** — книга переведена НЕ ЦЕЛИКОМ.
|
||||
- **`GhostRows > 0`** — есть строки вне текущего манифеста (в `Chunks` их нет по построению — состояние
|
||||
доступно только счётчиком).
|
||||
- **`ConfigDrift == true`** — текущий конфиг рендерит не тот снапшот, что несут строки.
|
||||
|
||||
**Как именно это показать читателю — решаешь сам и аргументируешь** (отказ писать · писать с явной
|
||||
пометкой · отдельный флаг команды). Что НЕ обсуждается: молчаливого полного файла на неполной книге
|
||||
быть не может.
|
||||
⚠ Ещё ДВА — ПОЮНИТНЫЕ, в счётчиках `BookExport` их НЕТ; `renderExport` считает их сам
|
||||
(`render.go:573-587`, баннеры `:601-622`) — повтори эту арифметику, не изобретай:
|
||||
- **withheld** — `Disposition != "pending" && FinalText == ""`: substantive-флаг или skip выше по
|
||||
конвейеру, текст сознательно не отгружен (D2: заражённый вывод не отгружается; `export.go:359-395`).
|
||||
`PendingUnits` его НЕ считает. `ApplyHeading` на пустой текст заголовок не клеит, так что глава молча
|
||||
начнётся со СЛЕДУЮЩЕГО юнита.
|
||||
- **incomplete** — `DroppedMembers > 0` при непустом `FinalText`: c-lite выбросил член юнита, текст
|
||||
настоящий, но с дырой без шва (`export.go:45-54`; причина — `DroppedReason`).
|
||||
|
||||
Живая посадка на оба — `acceptance` (числа в §4): при `pending_units=0` там удержан открывающий юнит
|
||||
книги и усечён юнит главы 10. Файл, сверенный только с тремя счётчиками, там выйдет «молча полным» —
|
||||
худший исход этого пака.
|
||||
|
||||
**Рамка ратифицирована ДО этого пака и бьёт «решаешь сам»:**
|
||||
- Контракт 14 (D39.99; `docs/architecture/14-api-contract/openapi.yaml:836-853`, `createExport`): неполная
|
||||
книга экспортируется — «may be exported, finished or not», а ЧТО содержит файл неполной книги — «not
|
||||
fixed here», то есть решается ЗДЕСЬ. Следствие: «только отказ» как единственное поведение НЕДОПУСТИМО —
|
||||
дверь платформы обязана уметь выдать неполную книгу.
|
||||
- D29.1(б): reader-facing выдача — fail-closed (0 красных для `structurally_complete`). D25.1 требует
|
||||
ратифицировать лестницу `run_complete / structurally_complete / quality_reviewed / publishable` ДО
|
||||
первого reader-facing артефакта; семантика «publishable» и право waiver — у владельца. **Решение
|
||||
оркестратора №20, не твой вопрос:** reader-facing артефакт — выдача читателю через дверь платформы;
|
||||
этот пак строит МЕХАНИЗМ файла с двумя обязательными поведениями ниже, а лестница ратифицируется
|
||||
владельцем между этим паком и дверью — по твоему вопросу в отчёте (какие ступени писатель обязан
|
||||
различать, что печатать на титуле и в имени файла). Поэтому лестницу в коде не изобретай и в
|
||||
`BookExport` не добавляй; молчаливый полный файл на книге с любой дырой НЕДОПУСТИМ.
|
||||
|
||||
**Делай РОВНО так:** файл с любой дырой (pending · withheld · incomplete) либо не пишется — отказ с
|
||||
перечнем дыр (глава/юнит/причина), либо пишется с явной пометкой неполноты на титуле и видимым маркером
|
||||
на месте КАЖДОЙ дыры. Оба поведения обязаны существовать (одно по умолчанию, другое флагом) — какое по
|
||||
умолчанию и как выглядят пометка и маркер, решаешь и аргументируешь; словесная форма для читателя —
|
||||
данные пары, не литерал в Go (§3.5). Для ghost и drift контракт ничего не фиксирует — отказ или пометка
|
||||
на твой довод. Три ограничения на форму:
|
||||
- ⛔ **Не в снапшот.** Загрузчик пака складывает байты КАЖДОГО присутствующего опционального файла в
|
||||
`pack.Version()` (`backend/internal/lang/langpack.go:289-298`), а она входит в snapshot-id стадий
|
||||
(`pipeline/snapshot.go:381`): новый файл-данные по этому контракту = resnapshot = перекупка каждой книги
|
||||
с `langpack_root`. Читательские слова — факт РЕНДЕРА, не перевода: канал для них НЕ складывается в
|
||||
`Version()` (отдельный не-фолдимый файл рядом с паком или вне пака) — форму решаешь и аргументируешь.
|
||||
Доказательство командой: `current_snapshot` экспорта копии minirun до и после добавления твоего
|
||||
файла-данных — тот же. ⚠ На стенде сдвиг НЕВИДИМ: minirun уже под дрейфом, `cmp` двух прогонов его не ловит.
|
||||
- ⛔ **Книга без langpack** (`acceptance`; `runner.go:346` → пак nil) словесной формы не имеет по
|
||||
построению — а это единственная реальная посадка на withheld/incomplete. Там пометка и маркер —
|
||||
несловесные (номер главы/юнита, символ) либо отказ; что именно — реши и аргументируй. Словесный
|
||||
fallback в Go запрещён, `langpack_root` в копию — запрещён (§3.3).
|
||||
- ⚠ Маркер дыры — НЕ в `FinalText` и не в `export.go`: провод платформы судит `translated`/`withheld`
|
||||
предикатом «текст непуст» (`platform/internal/ingest/export.go:104`).
|
||||
|
||||
### 3.3 Синтетический заголовок — НЕ твоя развилка (делай РОВНО так)
|
||||
|
||||
|
|
@ -73,61 +141,178 @@ EPUB — это zip, и в нём по умолчанию лежат時metки
|
|||
⛔ **Трогать `ApplyHeading` и любую из трёх точек ЗАПРЕЩЕНО.** Дизайн глав НЕ ратифицирован (D39.122
|
||||
п.2д: `heading` манифеста — ВРЕМЕННЫЙ рендер движка), а форма для него уже назначена: строка бэклога
|
||||
**160** «Структура глав, Этап 0», гейт которой пал словом владельца 15.08 (**D39.136 п.3**) — она едет
|
||||
ОТДЕЛЬНЫМ паком с мандатом максимального качества. Строка **201** — её родня и стоит «когда-нибудь (с
|
||||
160)». Этот пак её не решает и не предвосхищает.
|
||||
ОТДЕЛЬНЫМ, следующим бэкенд-паком с мандатом максимального качества; отсрочка — решение оркестратора
|
||||
(очередь №20 ставит писателя первым), не твоё. Строка **201** — её родня и стоит «когда-нибудь (с 160)».
|
||||
Этот пак её не решает и не предвосхищает.
|
||||
|
||||
**Что делать вместо:** заголовок для оглавления брать из УЖЕ персистированного манифеста —
|
||||
`ManifestChapter.Heading` (`backend/internal/pipeline/manifest.go:108`), тот же детерминированный литерал.
|
||||
Форма `tm-export-v1` не меняется, разбирать ничего не надо, оглавление есть. Если текст юнита при этом
|
||||
начинается тем же заголовком — срезать ИЗВЕСТНЫЕ байты префикса (`ApplyHeading` — чистая склейка
|
||||
`heading + "\n\n" + text`, `chunker.go:186-192`), а не угадывать эвристикой. ⚠ Если найдёшь причину, по
|
||||
которой так нельзя, — **пинг, а не своё решение**: вопрос гейчен владельцем.
|
||||
**Что делать вместо:** заголовок для оглавления — тот же детерминированный литерал, который `Export`
|
||||
УЖЕ держит в момент сборки записи: `u.Members[0].Heading` (`export.go:266`). Он приходит через
|
||||
`readModelChunks` (`manifest.go:550`) — из сайдкара `ManifestChapter.Heading` (`manifest.go:108`), когда
|
||||
тот есть, иначе из пере-нарезки; на обоих путях литерал один (проверено: экспорт minirun без сайдкара и
|
||||
с ним побайтово одинаков). ⚠ Сайдкар `<project_db>.manifest.json` (`manifest.go:137`) у
|
||||
minirun/acceptance/coldrun-a ОТСУТСТВУЕТ, `loadManifest` (`manifest.go:436-443`) на отсутствующий файл
|
||||
МОЛЧА отдаёт nil, а `ChunkExport` заголовка не несёт вовсе. Поэтому: писатель сайдкар НЕ читает, `tmctl
|
||||
manifest` НЕ требует и манифест побочно НЕ пишет. Донести заголовок до писателя — либо in-process
|
||||
(писатель в `pipeline`, рядом с `Export`), либо аддитивным полем `ChunkExport` (`heading`, `omitempty` —
|
||||
безопасно, §3.3-бис). Форма `tm-export-v1` при этом не меняется, разбирать текст не надо. Если текст
|
||||
юнита начинается тем же заголовком — срезать ИЗВЕСТНЫЕ байты префикса (`ApplyHeading` — чистая склейка
|
||||
`heading + "\n\n" + text`, `chunker.go:186-192`), только при `Heading != ""`, а не угадывать эвристикой.
|
||||
|
||||
⚠ **`Heading` бывает `""`, и это ратифицированно легальное состояние** (D39.100 п.1, К-3: зашитой формы
|
||||
«Глава N» не существует, легальна книга без глав вовсе; `manifest.go:102-103`). Два пути: книга без
|
||||
правила заголовка — `acceptance/book.yaml` не объявляет `langpack_root` (`runner.go:346`), и у неё `""` у
|
||||
ВСЕХ 25 глав; глава без структурного маркера — глава 1 той же книги остаётся `""` даже с правилом.
|
||||
⛔ Не «чини» это добавлением `langpack_root` в копию: состав книги от этого не меняется (те же 37/0/20 —
|
||||
экспорт джойнит по `(chapter, chunk_idx)`), но правило заголовка меняет ЭКСПОРТНЫЙ ТЕКСТ — `ApplyHeading`
|
||||
наклеит «Глава N» поверх модельного заголовка в 24 юнитах из 37 («Глава 3\n\nГлава 3: Иди-…»), файл
|
||||
перестанет быть той книгой, что переводилась, а посадка «книга без правила» исчезнет. Что стоит в
|
||||
оглавлении при `Heading == ""` — реши сам и аргументируй, при двух запретах: никакого словесного литерала
|
||||
в Go («Глава», «Chapter», на любом языке) — только данные (номер главы числом, маркер из данных пары);
|
||||
модельные заголовки внутри прозы («**Раздел второй…**», «### Шестой раздел…» и — при `Heading == ""` —
|
||||
«Глава 3: …» у юнитов 3/0, 12/0, 25/0 acceptance) не извлекать и не править — это строка 160; срез
|
||||
префикса только по `u.Members[0].Heading`, никогда по слову «Глава». Пустой текст ссылки в nav —
|
||||
невалидный EPUB. ⚠ Если найдёшь причину, по которой так нельзя, — **пинг, а
|
||||
не своё решение**: вопрос гейчен владельцем.
|
||||
|
||||
### 3.3-бис Метаданных книги в `BookExport` НЕТ — учти до кода
|
||||
|
||||
Структура несёт `Version`, `BookID`, `TotalUnits`, `PendingUnits`, `GhostRows`, `ConfigDrift`,
|
||||
`CurrentSnapshot`, `Chunks` — и **ни заголовка, ни языка, ни автора**. EPUB требует `dc:title`,
|
||||
`dc:language`, `dc:identifier`. Первые два достижимы из `config.Book` (`book.go:25-27`: `Title`,
|
||||
`SourceLang`, `TargetLang`); **автора в проекте нет вовсе** — источника для `dc:creator` не существует,
|
||||
и выдумывать его не надо. ⛔ `dc:language` берётся из `TargetLang`, а не литералом: пары — данные.
|
||||
**Решаешь сам:** расширять ли `BookExport` (⚠ добавление поля безопасно — платформа декодирует
|
||||
подмножеством и неизвестные поля игнорирует; ПЕРЕНОС существующего поля — нет) или брать метаданные
|
||||
мимо него.
|
||||
`BookExport` несёт `Version`, `BookID`, `TotalUnits`, `PendingUnits`, `GhostRows`, `ConfigDrift`,
|
||||
`CurrentSnapshot`, `Chunks` — и **ни заголовка, ни языка, ни автора**. Поля юнита — `ChunkExport`
|
||||
(`export.go:25-55`): `Chapter`, `ChunkIdx`, `SnapshotID`, `Disposition` (ok|flagged|pending), `FlagReason`,
|
||||
`Detail`, `FinalText`, `Source` (только `--pairs`), `DroppedMembers`/`DroppedReason`; заголовка нет — он
|
||||
внутри `FinalText` (§3.3).
|
||||
|
||||
**EPUB 3 требует ПЯТЬ вещей, не три:** `dc:identifier` (на него ссылается `unique-identifier` пакета),
|
||||
`dc:title`, `dc:language`, `<meta property="dcterms:modified">` в форме `CCYY-MM-DDThh:mm:ssZ` и
|
||||
nav-документ (`properties="nav"` в manifest). Фикстура `chunktest` несёт только `dc:title` — epubcheck даёт
|
||||
на ней 5 ошибок; она образец контейнера, не метаданных. ⛔ nav в spine НЕ класть: `ingest.go` читает
|
||||
каждый spine-документ как главу (проверено: nav в spine → лишняя глава), и круг §4 ломается.
|
||||
⛔ `dcterms:modified` — не `time.Now()` (§3.1): константа-эпоха либо величина из данных экспорта
|
||||
(например, из `CurrentSnapshot`) — выбор аргументируй. `dc:identifier` — детерминированная строка от
|
||||
`BookID`.
|
||||
|
||||
Заголовок и язык достижимы из `config.Book` (`book.go:25-27`: `Title`, `SourceLang`, `TargetLang`).
|
||||
⚠ `Title` — отображаемое имя книги, не заголовок на языке цели: на стенде у всех книг `title: 蛊真人`, из
|
||||
платформы приходит ввод пользователя или имя файла; поле входит в замороженный `BriefHash`
|
||||
(`book.go:337-345`). Бери как есть, назови ограничение в отчёте; ⛔ перевод названия не выдумывать (ни
|
||||
литералом, ни транслитерацией, ни платным вызовом) и `title` в book.yaml не менять (смена = re-bill).
|
||||
**Автора в проекте нет вовсе** — источника для `dc:creator` не существует, и выдумывать его не надо.
|
||||
⛔ `dc:language` берётся из `TargetLang`, а не литералом: пары — данные. **Решаешь сам:** расширять ли
|
||||
`BookExport`/`ChunkExport` (⚠ добавление поля безопасно: `DecodeExport` платформы — `json.Unmarshal` без
|
||||
`DisallowUnknownFields`, `platform/internal/ingest/export.go:78-80`; полигон формы не проверяет; ПЕРЕНОС
|
||||
существующего поля — нет) или брать метаданные мимо него.
|
||||
|
||||
### 3.4 Куда пишется файл — размечено, не додумывай
|
||||
|
||||
⚠ `tmctl` сегодня не пишет НИ ОДНОГО файла: греп `os.Create|os.WriteFile` по `cmd/tmctl/*.go` минус
|
||||
тесты пуст, всё идёт в stdout. Значит выходная поверхность — новая. **Реши и аргументируй:** флаг пути
|
||||
или stdout · поведение при существующем файле (перезапись или отказ) · один файл на книгу или на главу.
|
||||
Молча выбранная форма здесь дороже неверной: её потом читает платформенная дверь выдачи.
|
||||
`cmd/tmctl` сам файлов не пишет (греп `os.Create|os.WriteFile` по `cmd/tmctl/*.go` минус тесты пуст), но
|
||||
движок пишет их слоем ниже, и у них есть конвенция: сайдкары через `pipeline.writeFileAtomic`
|
||||
(`internal/pipeline/artifact.go:26`; используют `manifest.go:401`, `bankexport.go:152`, `mining.go`) — место
|
||||
рядом с `ProjectDB`, запись атомарная, путь ПУБЛИКУЕТСЯ в конверте `StatusArtifacts` (`status.go:115-145`),
|
||||
и платформа берёт пути ТОЛЬКО оттуда (`platform/internal/runner/artifacts.go:39`; закон шва
|
||||
`docs/architecture/17-seam-inbound-law.md` §1 — движок владеет путями своей схемы, платформа путь не
|
||||
выводит сама). Приор (опровергается аргументом): файл книги — такой же движковый артефакт:
|
||||
детерминированное место рядом с `ProjectDB`, атомарная запись, строка в `StatusArtifacts`; флаг явного
|
||||
пути — сверх этого, не вместо. **Реши и аргументируй:** форма вызова — новый глагол `tmctl` или флаг у
|
||||
`export` (глаголы — замороженный список `main.go:182`; форму потом читает дверь платформы) · флаг пути
|
||||
или конвенция · поведение при существующем файле (перезапись или отказ — у бэкапа отказ,
|
||||
`store/backup.go`) · публикуешь ли путь в конверте уже в этом паке. Коды выхода — замороженный словарь (`backend/cmd/tmctl/main.go:51-145`, запинен
|
||||
`exitcontract_test.go`): отказ §3.2 — новый `pipeline.RefusalClass` (`refusal.go`) с константой в полосе
|
||||
10–19, с доводом; чисел вне 0–5/10–19 не заводить. Молча выбранная форма здесь дороже неверной: её потом
|
||||
читает платформенная дверь выдачи.
|
||||
|
||||
### 3.5 Границы
|
||||
|
||||
- ⛔ **Платформу не трогать вообще.** Её дверь — следующая работа, не твоя.
|
||||
- ⛔ **Формат `tm-export-v1` не менять** без явного довода: его потребляет полигон.
|
||||
- ⛔ **Пары языков — данные.** Ревью-вопрос: «заработает ли пара, которой в репо НЕТ, без правки Go?»
|
||||
Если твой писатель знает слово «Глава» или порядок имени автора — это утечка.
|
||||
- **Внешние зависимости:** EPUB это zip + XHTML, и стандартная библиотека это умеет. Новая зависимость
|
||||
ради формата — решение с доводом и строкой в `STACK_DECISIONS`, а не побочный эффект.
|
||||
- ⛔ **Формат `tm-export-v1` не менять** без явного довода: его потребляет полигон и платформа
|
||||
(аддитивные поля по §3.3-бис — не смена формы).
|
||||
- ⛔ **Пары языков — данные.** Если твой писатель знает слово «Глава», «Оглавление» или порядок имени
|
||||
автора — это утечка.
|
||||
- **Внешние зависимости:** EPUB это zip + XHTML, и стандартная библиотека это умеет (`archive/zip`,
|
||||
`encoding/xml`); норма — «least mechanism» (`docs/architecture/12-go-style-notes.md` §1, строка 24: сперва
|
||||
stdlib, только потом своё/зависимость). ⚠ Носителя `STACK_DECISIONS` у бэкенда НЕТ — файлы с этим
|
||||
именем лежат у фронта и платформы, это чужие зоны: новая зависимость = довод в записке-плане §6 и
|
||||
пинг оркестратору ДО `go get`.
|
||||
|
||||
## 4. Самопроверка ИСПОЛНЕНИЕМ (без неё работа не принята)
|
||||
|
||||
- ⚠ **ДАННЫЕ: `coldrun-a` НЕ переведена — не строй на ней доказательство.** Проверено: у неё только
|
||||
волна `draft` (20 строк), финальной стадии нет ни у одного юнита, экспорт даёт **14/14 pending** и
|
||||
`CONFIG-DRIFT`. Вдобавок её конфиги несут абсолютные пути `/home/ubuntu/…` от прежней машины и
|
||||
ретайрнутые ключи, а схема БД v14 против головы v16 — `tmctl export` там падает четырьмя разными
|
||||
отказами подряд. **Годные книги с волной `edit`:** `books/gu-zhenren/minirun` (draft 20 / edit 14, схема
|
||||
v10) и `books/gu-zhenren/acceptance` (57/57, v7) — обе требуют миграции до v16. ⛔ **`books/` — отдельный
|
||||
git-репозиторий со своим origin: работай на КОПИИ в песочнице, оригиналы не мигрируй и не правь.**
|
||||
- **Файл открывается читалкой** — если есть чем. Проверено: на этой машине нет ни `calibre`, ни
|
||||
`epubcheck`, ни `java`, ни python-библиотек epub. Значит ось «издательский результат» механического
|
||||
верификатора пока не имеет: либо приноси свой (валидатор в песочнице), либо честно пиши в obstacle, что
|
||||
проверено структурно, а не читалкой. Не выдавай второе за первое.
|
||||
- **Круг замыкается:** файл, который ты написал, скорми обратно `internal/chunk/ingest.go` и покажи,
|
||||
что он разбирается. Это единственная механическая проверка, что писатель обратим чтению.
|
||||
- **Детерминизм:** два прогона → `cmp` побайтно. Покажи команду и результат.
|
||||
- **Три состояния §3.2** — по посадке на каждое: неполная книга, ghost-строка, дрейф конфига. Каждая
|
||||
обязана отработать так, как ты решил, и это видно в файле или в отказе.
|
||||
- ⚠ **ДАННЫЕ — замерено на копиях 30.08 (HEAD `8977001`), пере-прогони сам.** В `books/` нет ни одной
|
||||
книги, которую `tmctl export` берёт как есть, и ни одной без `CONFIG-DRIFT`.
|
||||
- **`coldrun-a` НЕ переведена — не строй на ней доказательство.** Только волна `draft` (20 строк
|
||||
`chunk_status`), финальной стадии нет ни у одного юнита; схема v14; после миграции экспорт даёт
|
||||
**14/14 pending** и drift. Годна лишь как естественный образец «pending».
|
||||
- **`minirun`** (схема v10; `chunk_status` draft 20 / edit 14) — после миграции **14/14 ok, pending 0,
|
||||
`config_drift: true`** (обе волны). Единственная «полная» реальная книга; заголовки есть
|
||||
(`langpack_root` задан → первые юниты начинаются «Глава N\n\n»). Рядом трекнуты `export.json`/`export.txt` —
|
||||
экспорт ТОЙ ЖЕ БД июльским бинарём (`config_drift: false`, старая форма без `export_version`):
|
||||
сегодняшний `tmctl` немигрированную v10 экспортировать отказывается (exit 13), так что это
|
||||
единственный до-миграционный оракул; после миграции `final_text`/`snapshot_id`/`disposition` обязаны
|
||||
совпасть 14/14 — покажи командой, это доказательство, что миграция и дрейф текст не тронули. Файлы
|
||||
не править.
|
||||
- **`acceptance`** (v7; `chunk_status` 57/57 строк — это НЕ юниты) — на HEAD экспорт даёт **37 юнитов,
|
||||
`ghost_rows: 20`, drift, 35 ok + withheld 1/0 + incomplete 10/0**; нарезка сменилась с июля, «57» на
|
||||
HEAD не существует, восстанавливать не надо. Заголовков нет (§3.3). Это посадка сразу на ghost,
|
||||
drift, withheld и incomplete.
|
||||
- **Дрейф на этом HEAD неустраним** — разошлись Go-константы снапшота (версии chunker/classifier/
|
||||
sanitizer/style_check) и промты; снять его может только платный перепрогон (`--resnapshot` = re-pay,
|
||||
⛔ не делать). Следствие для §3.2 — в записку-план, не в конец работы: чистый бездрейфовый файл на
|
||||
реальной книге НЕДОСТИЖИМ; чистый путь и pending доказывай на синтетике (харнессы ниже), реальные
|
||||
книги — как посадки на drift/ghost/withheld/incomplete. Выбрал «отказ при дрейфе» по умолчанию —
|
||||
реального EPUB у тебя не будет: заяви это в плане с доводом.
|
||||
- ⛔ **`books/` — отдельный git-репозиторий со своим origin, `.db` в нём ТРЕКАЮТСЯ; работай на копии в
|
||||
песочнице — но `cp -r` каталога КОПИЕЙ НЕ ЯВЛЯЕТСЯ.** `book.yaml`, `pipeline*.yaml` и `pairs/*.yaml`
|
||||
всех трёх книг несут АБСОЛЮТНЫЕ пути, и на этой машине они РЕЗОЛВЯТСЯ: `/home/ubuntu/books` — симлинк на
|
||||
оригинальный books-репозиторий, `/home/ubuntu/projects/textmachine/…` — ЧУЖОЙ ворктри (ветка polygon,
|
||||
живая сессия). `LoadBook` абсолютный путь не пере-рутит (`config/book.go:173`), `tmctl migrate`
|
||||
мигрирует `book.ProjectDB` на месте и кладёт `backups/` рядом (`migrate.go:83-84`); даже read-only
|
||||
`export`/`status` открывают WAL и создают `.lock`/`-wal`/`-shm` рядом с оригиналом. Порядок: `go build
|
||||
-o <песочница>/tmctl ./cmd/tmctl` из `backend/` → скопировать каталог книги + `gu-zhenren/langpack-extend`
|
||||
+ сиды `gu-zhenren/guzhenren-seed*.yaml` + исходный txt в песочницу → переписать КАЖДЫЙ абсолютный
|
||||
путь в копиях (`project_db` первым, затем `pipeline`, `source_file`, `glossary_seed`, `langpack_extend`,
|
||||
`models`, `langpack_root`, `pairs/zh-ru.yaml: prompts_root`, `pipeline.yaml: prompt_override` и
|
||||
`mining.contrast_path` — последний формально: файла в этом дереве нет, его открывает только майнер,
|
||||
`mining.go:58`) на песочницу и на `backend/` ЭТОГО ворктри →
|
||||
`grep -rn --include='*.yaml' '/home/ubuntu/books\|/home/ubuntu/projects/textmachine/' <копия>` обязан
|
||||
быть пуст (старые пути в `run-*.log` и в комментарии `langpack-extend/zh/surnames-compound.txt` движок
|
||||
не читает — не трогать) → только потом tmctl. Доказательство: `git -C books status --short -- gu-zhenren`
|
||||
до и после — одинаковый вывод, без новых `backups/`, `.lock`, `-wal`, `-shm` (весь `books/` не
|
||||
сравнивай — параллельная полигон-сессия пишет в `dovodka/` и `chtenie-rol/`; строки `D gu-zhenren/*/*.db-wal`/`-shm`
|
||||
стоят там ДО тебя — репозиторий трекает wal/shm).
|
||||
- **Хирургия конфигов В КОПИИ** (движок называет каждую причину сам, exit 10): у minirun и coldrun-a
|
||||
удалить ретайрнутые `mined_delta:`/`mined_rejects:` (D39.156 п.3); у acceptance в
|
||||
`pipeline-acceptance.yaml` убрать `context.stm_depth`/`overlap_tokens` и `prompt:` у обеих стадий
|
||||
(промты резолвятся конвенцией `<prompts_root>/<пара>/<роль>.md`) и положить рядом `pairs/zh-ru.yaml`
|
||||
(образец — `minirun/pairs/`, `prompts_root` на `backend/prompts` этого дерева). Все правки — списком
|
||||
в отчёт.
|
||||
- **Файл проходит валидатор EPUB — и это проверяется механически.** Проверено 30.08: `calibre`/
|
||||
`ebook-convert`, python-библиотек epub и CLI `zip` на машине нет, но **`java` ЕСТЬ** (`/usr/bin/java`,
|
||||
OpenJDK 21) и сеть доступна — epubcheck приносится одной командой в песочницу:
|
||||
`curl -sL -o epubcheck.zip https://github.com/w3c/epubcheck/releases/download/v5.3.0/epubcheck-5.3.0.zip && unzip -q epubcheck.zip && java -jar epubcheck-5.3.0/epubcheck.jar <book>.epub`
|
||||
(проверено: качается ~33 МБ, запускается, валидирует). Это официальный валидатор W3C — механический
|
||||
верификатор оси «издательский результат». Прогони им КАЖДЫЙ сданный EPUB, порог — `0 fatals / 0 errors`,
|
||||
warnings перечисли в отчёте с диспозицией; ошибки чинить у писателя, не глушить. Jar живёт в
|
||||
песочнице — в репозиторий и в зависимости не попадает. Не удалось принести — в obstacle команда и её
|
||||
вывод, и тогда «проверено структурно, не валидатором» — не выдавай второе за первое. ⚠ Валидатор — не
|
||||
читалка: «валиден» ≠ «открылся»; читалки на машине нет, эту ось так и помечай.
|
||||
- **Круг замыкается — РАВЕНСТВОМ, не парсом.** `chunk.IngestEncoded(<твой.epub>, "", "")` (epub-ветка
|
||||
языка и langpack не читает — `ingest.go:77-82`) и покажи командой: (а) `len(doc.Chapters)` == число глав
|
||||
книги — ингест кладёт в `doc.Chapters` КАЖДЫЙ XHTML-документ spine, пустой тоже (`ingest.go:534`;
|
||||
пустота лишь лишает его плотного номера, `:540`), поэтому nav/титул в spine не класть даже без
|
||||
текста, а глава без текста — легальная пустая глава; (б) абзацы каждой главы (разрез по пустым строкам, trim, пустые прочь —
|
||||
как `splitParagraphs`, `chunker.go:458`) совпадают с абзацами, которые положил писатель, с учётом
|
||||
решения §3.3 о заголовке. Абзац = свой блочный элемент (`<p>`); `<br/>` абзаца не даёт (`blockTags`,
|
||||
`ingest.go:614`); побайтово текст не совпадёт — сравнивай абзацы. `err == nil` сам по себе не
|
||||
доказывает ничего: вся книга одним документом «разбирается» одной главой.
|
||||
- **Детерминизм:** два прогона в двух ПРОЦЕССАХ → `cmp` побайтно. Покажи команду и результат.
|
||||
- **Пять состояний §3.2 — по посадке на каждое:** pending (coldrun-a или синтетика) · ghost, drift,
|
||||
withheld, incomplete (все четыре есть на acceptance) · чистая полная книга — только синтетика. Две
|
||||
посадки УЖЕ есть — не изобретай: `backend/internal/pipeline/export_test.go` `TestExportManifestPending`
|
||||
(`:186`) и `TestExportDetectsConfigDrift` (`:229`) на хелперах из `runner_test.go`. ⚠ Ghost-посадки в
|
||||
тестах НЕТ (греп `GhostRows` по `*_test.go` пуст) — строишь сам, отдельной строкой в отчёте.
|
||||
Golden-харнесс (`golden_test.go`) — гард wire пайплайна, не инструмент для твоего файла;
|
||||
`TM_UPDATE_GOLDEN` не трогать. Каждое состояние обязано отработать так, как ты решил, и это видно в
|
||||
файле или в отказе. Файл реальной книги, её копии и cmp-вывод — рабочее временное в песочнице: в
|
||||
отчёт идут команды и вывод; воспроизводимость на приёмке держит герметичный тест писателя в
|
||||
`backend/` на синтетической книге (`chunktest.BuildEPUB`, образец герметичного `Export` — `export_test.go`).
|
||||
- ⛔ **Править или удалять тест ради зелени НЕДОПУСТИМО (D39.121).** Это не абстракция: любой вариант,
|
||||
трогающий заголовок, красит `backend/internal/chunk/chunker_heading_test.go:121`, и самый дешёвый
|
||||
способ вернуть зелень — правка ассерта. Несогласие с тестом — пинг, а не правка.
|
||||
|
|
@ -139,32 +324,38 @@ EPUB — это zip, и в нём по умолчанию лежат時metки
|
|||
- **Разрешаю субагентов** (иначе дефолт харнесса тихо запретит). Модель задавай ЯВНО и знай, сколько
|
||||
их работает. Трудный вопрос — старшей модели.
|
||||
- ⚠ **Адверсариальный проход по своей готовой работе перед сдачей.** Направление: файл, молча
|
||||
притворяющийся полным; недетерминизм zip; заголовок, потерянный или задвоенный; кодировка и
|
||||
притворяющийся полным — в том числе при `pending_units=0` (пустая глава, дыра внутри юнита);
|
||||
`dcterms:modified` из часов; заголовок потерянный, задвоенный или пустой; nav в spine; кодировка и
|
||||
направление письма; книга из одного юнита и книга из нуля юнитов. **Пока идёт проход — дерево не
|
||||
двигается:** либо морозь, либо называй агенту коммит.
|
||||
|
||||
## 5. Оси ревью
|
||||
|
||||
**Издательский результат** (файл открывается и читается как книга) · **честность выдачи** (неполнота
|
||||
никогда не выглядит полнотой) · **детерминизм** (побайтовая воспроизводимость). Вправе заменить ось с
|
||||
аргументом.
|
||||
**Издательский результат** (файл проходит валидатор и читается как книга) · **честность выдачи**
|
||||
(неполнота никогда не выглядит полнотой) · **детерминизм** (побайтовая воспроизводимость). Вправе
|
||||
заменить ось с аргументом.
|
||||
|
||||
## 6. Записка-план и комплектность
|
||||
|
||||
До кода — короткая записка-план в `docs/PROGRESS.md`, секция «Бэкенд»: что берёшь, что не берёшь, чем
|
||||
докажешь. Перед сдачей — механическая сверка состава против §3 таблицей: пункт → сделано/нет → чем
|
||||
доказано. Пропуск подписывается пропуском. **Последний абзац отчёта оказался планом или обещанием?
|
||||
Сделай сейчас.**
|
||||
докажешь, какая политика §3.2 по умолчанию и почему. Перед сдачей — механическая сверка состава против
|
||||
§3 таблицей: пункт → сделано/нет → чем доказано. Пропуск подписывается пропуском. **Последний абзац
|
||||
отчёта оказался планом или обещанием? Сделай сейчас.**
|
||||
|
||||
## 7. Канал вопросов и право отказаться
|
||||
|
||||
Конфликт промта с кодом или доками — **пинг через владельца, не интерпретация**. Есть право сказать
|
||||
«этого делать не надо» с аргументом.
|
||||
|
||||
⚠ Правки задания по ходу работы доезжают ТОЛЬКО релеем через владельца — отдельным его сообщением.
|
||||
Что не пришло релеем — не заказ, даже если пришло по адресу из `/tmp/textmachine-channel`. Полученную
|
||||
правку эхо-подтверди и вынеси отдельным пунктом отчёта.
|
||||
|
||||
## 8. Заявление = команда
|
||||
|
||||
Любое число и любая категорика — **с командой, которой получены**. Приёмка пере-ранит: клейм без
|
||||
пере-прогона записывается как «со слов сессии».
|
||||
пере-прогона записывается как «со слов сессии». Утверждение без `file:line`/замера помечай словом
|
||||
«мнение» — эрудиция уликой не считается.
|
||||
|
||||
## 9. Эхо-протокол старта
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -22,7 +22,7 @@
|
|||
| Роль | Активный промт | Статус |
|
||||
|---|---|---|
|
||||
| Оркестратор | [ORCHESTRATOR_SESSION_PROMPT.md](ORCHESTRATOR_SESSION_PROMPT.md) | роль и нормы; счётчик роли — CURRENT-STATE |
|
||||
| Бэкенд | [BACKEND_BOOKWRITER_PACK_SESSION_PROMPT.md](BACKEND_BOOKWRITER_PACK_SESSION_PROMPT.md) | **НАПИСАН, ждёт запуска владельцем.** Пак «писатель книги» — строка **236**, единственный блокер очереди: переведённую книгу нельзя отдать файлом ни одной стороной. Оба рубежа пройдены; опровергатель нашёл **восемь неверных фактов и гейченную развилку**, всё применено — в том числе что EPUB-писатель в дереве УЖЕ ЕСТЬ (`chunktest/epub.go`), что `coldrun-a` не переведена и негодна как база, и что заголовок глав трогать НЕЛЬЗЯ (гейт строки 160, D39.136 п.3) |
|
||||
| Бэкенд | [BACKEND_BOOKWRITER_PACK_SESSION_PROMPT.md](BACKEND_BOOKWRITER_PACK_SESSION_PROMPT.md) | **ВЫДАН 30.08** сессии `textmachine-main-a6` (пинг оркестратора №20 по заказу владельца). Пак «писатель книги» — строка **236**, единственный блокер очереди: переведённую книгу нельзя отдать файлом ни одной стороной. ⚠ Перед выдачей — второй опровергатель (воркфлоу 5 линз + верификаторы, 100 агентов): **41 удержанная находка**, применены — «копия» каталога книги мигрирует ОРИГИНАЛ через симлинк `/home/ubuntu/books` · `java` на машине ЕСТЬ и epubcheck приносится · два поюнитных состояния withheld/incomplete + рамка D25.1/D29.1(б)/контракт 14 · сайдкар манифеста у книг ОТСУТСТВУЕТ · EPUB 3 требует `dcterms:modified` и nav · `acceptance` на HEAD — 37 юнитов при 20 ghost, не «57/57» |
|
||||
| Платформа | активного НЕТ | пак `sqlc` ПРИНЯТ И ЗАЛЕНДЖЕН 29.08 (**D39.172**): 40 запросов на типизированный слой, пин 1.31.1, `sqlc diff` в `make check`. ⚠ Решение принято НЕ доводом покрытия (3 из 42), а шестью ВЫЖИВШИМИ мутациями: `sqlgate` не видит Go-сторону вызова. Промт отработан — `archive/prompts/`. Открыто строками: `Touch` без проверки затронутых строк · `observe.go` неконвертируем |
|
||||
| Полигон | [POLYGON_EXP2223_REDO_SESSION_PROMPT.md](POLYGON_EXP2223_REDO_SESSION_PROMPT.md) (отложенный — [POLYGON_PACKAGE4_SESSION_PROMPT.md](POLYGON_PACKAGE4_SESSION_PROMPT.md), строка 85) | фаза Д ИДЁТ; ⚠ живой носитель курса — в `eval/dovodka/`, какой именно называет зона (⚠ [POLYGON_PHASE_D_HANDOFF.md](POLYGON_PHASE_D_HANDOFF.md) — перекрытый снимок, читать не как курс) |
|
||||
| Фронт | активного НЕТ | **ЗОНА ЗАМОРОЖЕНА** (D39.136 п.2 + D39.147: разморозка отдельным словом владельца, не привязана к P7); перечень первого касания — в зонном журнале |
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue